"); break; case ODDBALL_TYPE: { if (IsUndefined()) accumulator->Add(""); else if (IsTheHole()) accumulator->Add(""); else if (IsNull()) accumulator->Add(""); else if (IsTrue()) accumulator->Add(""); else if (IsFalse()) accumulator->Add(""); else accumulator->Add(""); break; } case HEAP_NUMBER_TYPE: accumulator->Add("HeapNumberPrint(accumulator); accumulator->Put('>'); break; case JS_PROXY_TYPE: accumulator->Add(""); break; case JS_FUNCTION_PROXY_TYPE: accumulator->Add(""); break; case FOREIGN_TYPE: accumulator->Add(""); break; case JS_GLOBAL_PROPERTY_CELL_TYPE: accumulator->Add("Cell for "); JSGlobalPropertyCell::cast(this)->value()->ShortPrint(accumulator); break; default: accumulator->Add("", map()->instance_type()); break; } } void HeapObject::Iterate(ObjectVisitor* v) { // Handle header IteratePointer(v, kMapOffset); // Handle object body Map* m = map(); IterateBody(m->instance_type(), SizeFromMap(m), v); } void HeapObject::IterateBody(InstanceType type, int object_size, ObjectVisitor* v) { // Avoiding ::cast(this) because it accesses the map pointer field. // During GC, the map pointer field is encoded. if (type < FIRST_NONSTRING_TYPE) { switch (type & kStringRepresentationMask) { case kSeqStringTag: break; case kConsStringTag: ConsString::BodyDescriptor::IterateBody(this, v); break; case kSlicedStringTag: SlicedString::BodyDescriptor::IterateBody(this, v); break; case kExternalStringTag: if ((type & kStringEncodingMask) == kAsciiStringTag) { reinterpret_cast(this)-> ExternalAsciiStringIterateBody(v); } else { reinterpret_cast(this)-> ExternalTwoByteStringIterateBody(v); } break; } return; } switch (type) { case FIXED_ARRAY_TYPE: FixedArray::BodyDescriptor::IterateBody(this, object_size, v); break; case FIXED_DOUBLE_ARRAY_TYPE: break; case JS_OBJECT_TYPE: case JS_CONTEXT_EXTENSION_OBJECT_TYPE: case JS_VALUE_TYPE: case JS_DATE_TYPE: case JS_ARRAY_TYPE: case JS_SET_TYPE: case JS_MAP_TYPE: case JS_WEAK_MAP_TYPE: case JS_REGEXP_TYPE: case JS_GLOBAL_PROXY_TYPE: case JS_GLOBAL_OBJECT_TYPE: case JS_BUILTINS_OBJECT_TYPE: case JS_MESSAGE_OBJECT_TYPE: JSObject::BodyDescriptor::IterateBody(this, object_size, v); break; case JS_FUNCTION_TYPE: reinterpret_cast(this) ->JSFunctionIterateBody(object_size, v); break; case ODDBALL_TYPE: Oddball::BodyDescriptor::IterateBody(this, v); break; case JS_PROXY_TYPE: JSProxy::BodyDescriptor::IterateBody(this, v); break; case JS_FUNCTION_PROXY_TYPE: JSFunctionProxy::BodyDescriptor::IterateBody(this, v); break; case FOREIGN_TYPE: reinterpret_cast(this)->ForeignIterateBody(v); break; case MAP_TYPE: Map::BodyDescriptor::IterateBody(this, v); break; case CODE_TYPE: reinterpret_cast(this)->CodeIterateBody(v); break; case JS_GLOBAL_PROPERTY_CELL_TYPE: JSGlobalPropertyCell::BodyDescriptor::IterateBody(this, v); break; case HEAP_NUMBER_TYPE: case FILLER_TYPE: case BYTE_ARRAY_TYPE: case FREE_SPACE_TYPE: case EXTERNAL_PIXEL_ARRAY_TYPE: case EXTERNAL_BYTE_ARRAY_TYPE: case EXTERNAL_UNSIGNED_BYTE_ARRAY_TYPE: case EXTERNAL_SHORT_ARRAY_TYPE: case EXTERNAL_UNSIGNED_SHORT_ARRAY_TYPE: case EXTERNAL_INT_ARRAY_TYPE: case EXTERNAL_UNSIGNED_INT_ARRAY_TYPE: case EXTERNAL_FLOAT_ARRAY_TYPE: case EXTERNAL_DOUBLE_ARRAY_TYPE: break; case SHARED_FUNCTION_INFO_TYPE: { SharedFunctionInfo* shared = reinterpret_cast(this); shared->SharedFunctionInfoIterateBody(v); break; } #define MAKE_STRUCT_CASE(NAME, Name, name) \ case NAME##_TYPE: STRUCT_LIST(MAKE_STRUCT_CASE) #undef MAKE_STRUCT_CASE StructBodyDescriptor::IterateBody(this, object_size, v); break; default: PrintF("Unknown type: %d\n", type); UNREACHABLE(); } } Object* HeapNumber::HeapNumberToBoolean() { // NaN, +0, and -0 should return the false object #if __BYTE_ORDER == __LITTLE_ENDIAN union IeeeDoubleLittleEndianArchType u; #elif __BYTE_ORDER == __BIG_ENDIAN union IeeeDoubleBigEndianArchType u; #endif u.d = value(); if (u.bits.exp == 2047) { // Detect NaN for IEEE double precision floating point. if ((u.bits.man_low | u.bits.man_high) != 0) return GetHeap()->false_value(); } if (u.bits.exp == 0) { // Detect +0, and -0 for IEEE double precision floating point. if ((u.bits.man_low | u.bits.man_high) == 0) return GetHeap()->false_value(); } return GetHeap()->true_value(); } void HeapNumber::HeapNumberPrint(FILE* out) { PrintF(out, "%.16g", Number()); } void HeapNumber::HeapNumberPrint(StringStream* accumulator) { // The Windows version of vsnprintf can allocate when printing a %g string // into a buffer that may not be big enough. We don't want random memory // allocation when producing post-crash stack traces, so we print into a // buffer that is plenty big enough for any floating point number, then // print that using vsnprintf (which may truncate but never allocate if // there is no more space in the buffer). EmbeddedVector buffer; OS::SNPrintF(buffer, "%.16g", Number()); accumulator->Add("%s", buffer.start()); } String* JSReceiver::class_name() { if (IsJSFunction() && IsJSFunctionProxy()) { return GetHeap()->function_class_symbol(); } if (map()->constructor()->IsJSFunction()) { JSFunction* constructor = JSFunction::cast(map()->constructor()); return String::cast(constructor->shared()->instance_class_name()); } // If the constructor is not present, return "Object". return GetHeap()->Object_symbol(); } String* JSReceiver::constructor_name() { if (map()->constructor()->IsJSFunction()) { JSFunction* constructor = JSFunction::cast(map()->constructor()); String* name = String::cast(constructor->shared()->name()); if (name->length() > 0) return name; String* inferred_name = constructor->shared()->inferred_name(); if (inferred_name->length() > 0) return inferred_name; Object* proto = GetPrototype(); if (proto->IsJSObject()) return JSObject::cast(proto)->constructor_name(); } // TODO(rossberg): what about proxies? // If the constructor is not present, return "Object". return GetHeap()->Object_symbol(); } MaybeObject* JSObject::AddFastPropertyUsingMap(Map* new_map, String* name, Object* value) { int index = new_map->PropertyIndexFor(name); if (map()->unused_property_fields() == 0) { ASSERT(map()->unused_property_fields() == 0); int new_unused = new_map->unused_property_fields(); Object* values; { MaybeObject* maybe_values = properties()->CopySize(properties()->length() + new_unused + 1); if (!maybe_values->ToObject(&values)) return maybe_values; } set_properties(FixedArray::cast(values)); } set_map(new_map); return FastPropertyAtPut(index, value); } static bool IsIdentifier(UnicodeCache* cache, unibrow::CharacterStream* buffer) { // Checks whether the buffer contains an identifier (no escape). if (!buffer->has_more()) return false; if (!cache->IsIdentifierStart(buffer->GetNext())) { return false; } while (buffer->has_more()) { if (!cache->IsIdentifierPart(buffer->GetNext())) { return false; } } return true; } MaybeObject* JSObject::AddFastProperty(String* name, Object* value, PropertyAttributes attributes) { ASSERT(!IsJSGlobalProxy()); // Normalize the object if the name is an actual string (not the // hidden symbols) and is not a real identifier. Isolate* isolate = GetHeap()->isolate(); StringInputBuffer buffer(name); if (!IsIdentifier(isolate->unicode_cache(), &buffer) && name != isolate->heap()->hidden_symbol()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return AddSlowProperty(name, value, attributes); } DescriptorArray* old_descriptors = map()->instance_descriptors(); // Compute the new index for new field. int index = map()->NextFreePropertyIndex(); // Allocate new instance descriptors with (name, index) added FieldDescriptor new_field(name, index, attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = old_descriptors->CopyInsert(&new_field, REMOVE_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } } // Only allow map transition if the object isn't the global object and there // is not a transition for the name, or there's a transition for the name but // it's unrelated to properties. int descriptor_index = old_descriptors->Search(name); // Element transitions are stored in the descriptor for property "", which is // not a identifier and should have forced a switch to slow properties above. ASSERT(descriptor_index == DescriptorArray::kNotFound || old_descriptors->GetType(descriptor_index) != ELEMENTS_TRANSITION); bool can_insert_transition = descriptor_index == DescriptorArray::kNotFound || old_descriptors->GetType(descriptor_index) == ELEMENTS_TRANSITION; bool allow_map_transition = can_insert_transition && (isolate->context()->global_context()->object_function()->map() != map()); ASSERT(index < map()->inobject_properties() || (index - map()->inobject_properties()) < properties()->length() || map()->unused_property_fields() == 0); // Allocate a new map for the object. Object* r; { MaybeObject* maybe_r = map()->CopyDropDescriptors(); if (!maybe_r->ToObject(&r)) return maybe_r; } Map* new_map = Map::cast(r); if (allow_map_transition) { // Allocate new instance descriptors for the old map with map transition. MapTransitionDescriptor d(name, Map::cast(new_map), attributes); Object* r; { MaybeObject* maybe_r = old_descriptors->CopyInsert(&d, KEEP_TRANSITIONS); if (!maybe_r->ToObject(&r)) return maybe_r; } old_descriptors = DescriptorArray::cast(r); } if (map()->unused_property_fields() == 0) { if (properties()->length() > MaxFastProperties()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return AddSlowProperty(name, value, attributes); } // Make room for the new value Object* values; { MaybeObject* maybe_values = properties()->CopySize(properties()->length() + kFieldsAdded); if (!maybe_values->ToObject(&values)) return maybe_values; } set_properties(FixedArray::cast(values)); new_map->set_unused_property_fields(kFieldsAdded - 1); } else { new_map->set_unused_property_fields(map()->unused_property_fields() - 1); } // We have now allocated all the necessary objects. // All the changes can be applied at once, so they are atomic. map()->set_instance_descriptors(old_descriptors); new_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); set_map(new_map); return FastPropertyAtPut(index, value); } MaybeObject* JSObject::AddConstantFunctionProperty( String* name, JSFunction* function, PropertyAttributes attributes) { // Allocate new instance descriptors with (name, function) added ConstantFunctionDescriptor d(name, function, attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = map()->instance_descriptors()->CopyInsert(&d, REMOVE_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } } // Allocate a new map for the object. Object* new_map; { MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; } DescriptorArray* descriptors = DescriptorArray::cast(new_descriptors); Map::cast(new_map)->set_instance_descriptors(descriptors); Map* old_map = map(); set_map(Map::cast(new_map)); // If the old map is the global object map (from new Object()), // then transitions are not added to it, so we are done. Heap* heap = GetHeap(); if (old_map == heap->isolate()->context()->global_context()-> object_function()->map()) { return function; } // Do not add CONSTANT_TRANSITIONS to global objects if (IsGlobalObject()) { return function; } // Add a CONSTANT_TRANSITION descriptor to the old map, // so future assignments to this property on other objects // of the same type will create a normal field, not a constant function. // Don't do this for special properties, with non-trival attributes. if (attributes != NONE) { return function; } ConstTransitionDescriptor mark(name, Map::cast(new_map)); { MaybeObject* maybe_new_descriptors = old_map->instance_descriptors()->CopyInsert(&mark, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { // We have accomplished the main goal, so return success. return function; } } old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return function; } // Add property in slow mode MaybeObject* JSObject::AddSlowProperty(String* name, Object* value, PropertyAttributes attributes) { ASSERT(!HasFastProperties()); StringDictionary* dict = property_dictionary(); Object* store_value = value; if (IsGlobalObject()) { // In case name is an orphaned property reuse the cell. int entry = dict->FindEntry(name); if (entry != StringDictionary::kNotFound) { store_value = dict->ValueAt(entry); JSGlobalPropertyCell::cast(store_value)->set_value(value); // Assign an enumeration index to the property and update // SetNextEnumerationIndex. int index = dict->NextEnumerationIndex(); PropertyDetails details = PropertyDetails(attributes, NORMAL, index); dict->SetNextEnumerationIndex(index + 1); dict->SetEntry(entry, name, store_value, details); return value; } Heap* heap = GetHeap(); { MaybeObject* maybe_store_value = heap->AllocateJSGlobalPropertyCell(value); if (!maybe_store_value->ToObject(&store_value)) return maybe_store_value; } JSGlobalPropertyCell::cast(store_value)->set_value(value); } PropertyDetails details = PropertyDetails(attributes, NORMAL); Object* result; { MaybeObject* maybe_result = dict->Add(name, store_value, details); if (!maybe_result->ToObject(&result)) return maybe_result; } if (dict != result) set_properties(StringDictionary::cast(result)); return value; } MaybeObject* JSObject::AddProperty(String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); Heap* heap = GetHeap(); if (!map_of_this->is_extensible()) { if (strict_mode == kNonStrictMode) { return value; } else { Handle args[1] = {Handle(name)}; return heap->isolate()->Throw( *FACTORY->NewTypeError("object_not_extensible", HandleVector(args, 1))); } } if (HasFastProperties()) { // Ensure the descriptor array does not get too big. if (map_of_this->instance_descriptors()->number_of_descriptors() < DescriptorArray::kMaxNumberOfDescriptors) { if (value->IsJSFunction()) { return AddConstantFunctionProperty(name, JSFunction::cast(value), attributes); } else { return AddFastProperty(name, value, attributes); } } else { // Normalize the object to prevent very large instance descriptors. // This eliminates unwanted N^2 allocation and lookup behavior. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } } } return AddSlowProperty(name, value, attributes); } MaybeObject* JSObject::SetPropertyPostInterceptor( String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsFound()) { // An existing property, a map transition or a null descriptor was // found. Use set property to handle all these cases. return SetProperty(&result, name, value, attributes, strict_mode); } bool found = false; MaybeObject* result_object; result_object = SetPropertyWithCallbackSetterInPrototypes(name, value, attributes, &found, strict_mode); if (found) return result_object; // Add a new real property. return AddProperty(name, value, attributes, strict_mode); } MaybeObject* JSObject::ReplaceSlowProperty(String* name, Object* value, PropertyAttributes attributes) { StringDictionary* dictionary = property_dictionary(); int old_index = dictionary->FindEntry(name); int new_enumeration_index = 0; // 0 means "Use the next available index." if (old_index != -1) { // All calls to ReplaceSlowProperty have had all transitions removed. ASSERT(!dictionary->ContainsTransition(old_index)); new_enumeration_index = dictionary->DetailsAt(old_index).index(); } PropertyDetails new_details(attributes, NORMAL, new_enumeration_index); return SetNormalizedProperty(name, value, new_details); } MaybeObject* JSObject::ConvertDescriptorToFieldAndMapTransition( String* name, Object* new_value, PropertyAttributes attributes) { Map* old_map = map(); Object* result; { MaybeObject* maybe_result = ConvertDescriptorToField(name, new_value, attributes); if (!maybe_result->ToObject(&result)) return maybe_result; } // If we get to this point we have succeeded - do not return failure // after this point. Later stuff is optional. if (!HasFastProperties()) { return result; } // Do not add transitions to the map of "new Object()". if (map() == GetIsolate()->context()->global_context()-> object_function()->map()) { return result; } MapTransitionDescriptor transition(name, map(), attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = old_map->instance_descriptors()-> CopyInsert(&transition, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return result; // Yes, return _result_. } } old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return result; } MaybeObject* JSObject::ConvertDescriptorToField(String* name, Object* new_value, PropertyAttributes attributes) { if (map()->unused_property_fields() == 0 && properties()->length() > MaxFastProperties()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return ReplaceSlowProperty(name, new_value, attributes); } int index = map()->NextFreePropertyIndex(); FieldDescriptor new_field(name, index, attributes); // Make a new DescriptorArray replacing an entry with FieldDescriptor. Object* descriptors_unchecked; { MaybeObject* maybe_descriptors_unchecked = map()->instance_descriptors()-> CopyInsert(&new_field, REMOVE_TRANSITIONS); if (!maybe_descriptors_unchecked->ToObject(&descriptors_unchecked)) { return maybe_descriptors_unchecked; } } DescriptorArray* new_descriptors = DescriptorArray::cast(descriptors_unchecked); // Make a new map for the object. Object* new_map_unchecked; { MaybeObject* maybe_new_map_unchecked = map()->CopyDropDescriptors(); if (!maybe_new_map_unchecked->ToObject(&new_map_unchecked)) { return maybe_new_map_unchecked; } } Map* new_map = Map::cast(new_map_unchecked); new_map->set_instance_descriptors(new_descriptors); // Make new properties array if necessary. FixedArray* new_properties = 0; // Will always be NULL or a valid pointer. int new_unused_property_fields = map()->unused_property_fields() - 1; if (map()->unused_property_fields() == 0) { new_unused_property_fields = kFieldsAdded - 1; Object* new_properties_object; { MaybeObject* maybe_new_properties_object = properties()->CopySize(properties()->length() + kFieldsAdded); if (!maybe_new_properties_object->ToObject(&new_properties_object)) { return maybe_new_properties_object; } } new_properties = FixedArray::cast(new_properties_object); } // Update pointers to commit changes. // Object points to the new map. new_map->set_unused_property_fields(new_unused_property_fields); set_map(new_map); if (new_properties) { set_properties(FixedArray::cast(new_properties)); } return FastPropertyAtPut(index, new_value); } MaybeObject* JSObject::SetPropertyWithInterceptor( String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle this_handle(this); Handle name_handle(name); Handle value_handle(value, isolate); Handle interceptor(GetNamedInterceptor()); if (!interceptor->setter()->IsUndefined()) { LOG(isolate, ApiNamedPropertyAccess("interceptor-named-set", this, name)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::NamedPropertySetter setter = v8::ToCData(interceptor->setter()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); Handle value_unhole(value->IsTheHole() ? isolate->heap()->undefined_value() : value, isolate); result = setter(v8::Utils::ToLocal(name_handle), v8::Utils::ToLocal(value_unhole), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) return *value_handle; } MaybeObject* raw_result = this_handle->SetPropertyPostInterceptor(*name_handle, *value_handle, attributes, strict_mode); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } Handle JSReceiver::SetProperty(Handle object, Handle key, Handle value, PropertyAttributes attributes, StrictModeFlag strict_mode) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->SetProperty(*key, *value, attributes, strict_mode), Object); } MaybeObject* JSReceiver::SetProperty(String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { LookupResult result(GetIsolate()); LocalLookup(name, &result); return SetProperty(&result, name, value, attributes, strict_mode); } MaybeObject* JSObject::SetPropertyWithCallback(Object* structure, String* name, Object* value, JSObject* holder, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); // We should never get here to initialize a const with the hole // value since a const declaration would conflict with the setter. ASSERT(!value->IsTheHole()); Handle value_handle(value, isolate); // To accommodate both the old and the new api we switch on the // data structure used to store the callbacks. Eventually foreign // callbacks should be phased out. if (structure->IsForeign()) { AccessorDescriptor* callback = reinterpret_cast( Foreign::cast(structure)->foreign_address()); MaybeObject* obj = (callback->setter)(this, value, callback->data); RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (obj->IsFailure()) return obj; return *value_handle; } if (structure->IsAccessorInfo()) { // api style callbacks AccessorInfo* data = AccessorInfo::cast(structure); Object* call_obj = data->setter(); v8::AccessorSetter call_fun = v8::ToCData(call_obj); if (call_fun == NULL) return value; Handle key(name); LOG(isolate, ApiNamedPropertyAccess("store", this, name)); CustomArguments args(isolate, data->data(), this, JSObject::cast(holder)); v8::AccessorInfo info(args.end()); { // Leaving JavaScript. VMState state(isolate, EXTERNAL); call_fun(v8::Utils::ToLocal(key), v8::Utils::ToLocal(value_handle), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); return *value_handle; } if (structure->IsAccessorPair()) { Object* setter = AccessorPair::cast(structure)->setter(); if (setter->IsSpecFunction()) { // TODO(rossberg): nicer would be to cast to some JSCallable here... return SetPropertyWithDefinedSetter(JSReceiver::cast(setter), value); } else { if (strict_mode == kNonStrictMode) { return value; } Handle key(name); Handle holder_handle(holder, isolate); Handle args[2] = { key, holder_handle }; return isolate->Throw( *isolate->factory()->NewTypeError("no_setter_in_callback", HandleVector(args, 2))); } } UNREACHABLE(); return NULL; } MaybeObject* JSReceiver::SetPropertyWithDefinedSetter(JSReceiver* setter, Object* value) { Isolate* isolate = GetIsolate(); Handle value_handle(value, isolate); Handle fun(setter, isolate); Handle self(this, isolate); #ifdef ENABLE_DEBUGGER_SUPPORT Debug* debug = isolate->debug(); // Handle stepping into a setter if step into is active. // TODO(rossberg): should this apply to getters that are function proxies? if (debug->StepInActive() && fun->IsJSFunction()) { debug->HandleStepIn( Handle::cast(fun), Handle::null(), 0, false); } #endif bool has_pending_exception; Handle argv[] = { value_handle }; Execution::Call(fun, self, ARRAY_SIZE(argv), argv, &has_pending_exception); // Check for pending exception and return the result. if (has_pending_exception) return Failure::Exception(); return *value_handle; } void JSObject::LookupCallbackSetterInPrototypes(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = pt->GetPrototype()) { if (pt->IsJSProxy()) { return result->HandlerResult(JSProxy::cast(pt)); } JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); if (result->IsProperty()) { if (result->type() == CALLBACKS && !result->IsReadOnly()) return; // Found non-callback or read-only callback, stop looking. break; } } result->NotFound(); } MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( uint32_t index, Object* value, bool* found, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = pt->GetPrototype()) { if (pt->IsJSProxy()) { String* name; MaybeObject* maybe = GetHeap()->Uint32ToString(index); if (!maybe->To(&name)) { *found = true; // Force abort return maybe; } return JSProxy::cast(pt)->SetPropertyWithHandlerIfDefiningSetter( name, value, NONE, strict_mode, found); } if (!JSObject::cast(pt)->HasDictionaryElements()) { continue; } SeededNumberDictionary* dictionary = JSObject::cast(pt)->element_dictionary(); int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { PropertyDetails details = dictionary->DetailsAt(entry); if (details.type() == CALLBACKS) { *found = true; return SetElementWithCallback(dictionary->ValueAt(entry), index, value, JSObject::cast(pt), strict_mode); } } } *found = false; return heap->the_hole_value(); } MaybeObject* JSObject::SetPropertyWithCallbackSetterInPrototypes( String* name, Object* value, PropertyAttributes attributes, bool* found, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); // We could not find a local property so let's check whether there is an // accessor that wants to handle the property. LookupResult accessor_result(heap->isolate()); LookupCallbackSetterInPrototypes(name, &accessor_result); if (accessor_result.IsFound()) { *found = true; if (accessor_result.type() == CALLBACKS) { return SetPropertyWithCallback(accessor_result.GetCallbackObject(), name, value, accessor_result.holder(), strict_mode); } else if (accessor_result.type() == HANDLER) { // There is a proxy in the prototype chain. Invoke its // getPropertyDescriptor trap. bool found = false; // SetPropertyWithHandlerIfDefiningSetter can cause GC, // make sure to use the handlified references after calling // the function. Handle self(this); Handle hname(name); Handle hvalue(value); MaybeObject* result = accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter( name, value, attributes, strict_mode, &found); if (found) return result; // The proxy does not define the property as an accessor. // Consequently, it has no effect on setting the receiver. return self->AddProperty(*hname, *hvalue, attributes, strict_mode); } } *found = false; return heap->the_hole_value(); } void JSObject::LookupInDescriptor(String* name, LookupResult* result) { DescriptorArray* descriptors = map()->instance_descriptors(); int number = descriptors->SearchWithCache(name); if (number != DescriptorArray::kNotFound) { result->DescriptorResult(this, descriptors->GetDetails(number), number); } else { result->NotFound(); } } void Map::LookupInDescriptors(JSObject* holder, String* name, LookupResult* result) { DescriptorArray* descriptors = instance_descriptors(); DescriptorLookupCache* cache = GetHeap()->isolate()->descriptor_lookup_cache(); int number = cache->Lookup(descriptors, name); if (number == DescriptorLookupCache::kAbsent) { number = descriptors->Search(name); cache->Update(descriptors, name, number); } if (number != DescriptorArray::kNotFound) { result->DescriptorResult(holder, descriptors->GetDetails(number), number); } else { result->NotFound(); } } static bool ContainsMap(MapHandleList* maps, Handle map) { ASSERT(!map.is_null()); for (int i = 0; i < maps->length(); ++i) { if (!maps->at(i).is_null() && maps->at(i).is_identical_to(map)) return true; } return false; } template static Handle MaybeNull(T* p) { if (p == NULL) return Handle::null(); return Handle(p); } Handle Map::FindTransitionedMap(MapHandleList* candidates) { ElementsKind elms_kind = elements_kind(); if (elms_kind == FAST_DOUBLE_ELEMENTS) { bool dummy = true; Handle fast_map = MaybeNull(LookupElementsTransitionMap(FAST_ELEMENTS, &dummy)); if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { return fast_map; } return Handle::null(); } if (elms_kind == FAST_SMI_ONLY_ELEMENTS) { bool dummy = true; Handle double_map = MaybeNull(LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, &dummy)); // In the current implementation, if the DOUBLE map doesn't exist, the // FAST map can't exist either. if (double_map.is_null()) return Handle::null(); Handle fast_map = MaybeNull(double_map->LookupElementsTransitionMap(FAST_ELEMENTS, &dummy)); if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { return fast_map; } if (ContainsMap(candidates, double_map)) return double_map; } return Handle::null(); } static Map* GetElementsTransitionMapFromDescriptor(Object* descriptor_contents, ElementsKind elements_kind) { if (descriptor_contents->IsMap()) { Map* map = Map::cast(descriptor_contents); if (map->elements_kind() == elements_kind) { return map; } return NULL; } FixedArray* map_array = FixedArray::cast(descriptor_contents); for (int i = 0; i < map_array->length(); ++i) { Object* current = map_array->get(i); // Skip undefined slots, they are sentinels for reclaimed maps. if (!current->IsUndefined()) { Map* current_map = Map::cast(map_array->get(i)); if (current_map->elements_kind() == elements_kind) { return current_map; } } } return NULL; } static MaybeObject* AddElementsTransitionMapToDescriptor( Object* descriptor_contents, Map* new_map) { // Nothing was in the descriptor for an ELEMENTS_TRANSITION, // simply add the map. if (descriptor_contents == NULL) { return new_map; } // There was already a map in the descriptor, create a 2-element FixedArray // to contain the existing map plus the new one. FixedArray* new_array; Heap* heap = new_map->GetHeap(); if (descriptor_contents->IsMap()) { // Must tenure, DescriptorArray expects no new-space objects. MaybeObject* maybe_new_array = heap->AllocateFixedArray(2, TENURED); if (!maybe_new_array->To(&new_array)) { return maybe_new_array; } new_array->set(0, descriptor_contents); new_array->set(1, new_map); return new_array; } // The descriptor already contained a list of maps for different ElementKinds // of ELEMENTS_TRANSITION, first check the existing array for an undefined // slot, and if that's not available, create a FixedArray to hold the existing // maps plus the new one and fill it in. FixedArray* array = FixedArray::cast(descriptor_contents); for (int i = 0; i < array->length(); ++i) { if (array->get(i)->IsUndefined()) { array->set(i, new_map); return array; } } // Must tenure, DescriptorArray expects no new-space objects. MaybeObject* maybe_new_array = heap->AllocateFixedArray(array->length() + 1, TENURED); if (!maybe_new_array->To(&new_array)) { return maybe_new_array; } int i = 0; while (i < array->length()) { new_array->set(i, array->get(i)); ++i; } new_array->set(i, new_map); return new_array; } String* Map::elements_transition_sentinel_name() { return GetHeap()->empty_symbol(); } Object* Map::GetDescriptorContents(String* sentinel_name, bool* safe_to_add_transition) { // Get the cached index for the descriptors lookup, or find and cache it. DescriptorArray* descriptors = instance_descriptors(); DescriptorLookupCache* cache = GetIsolate()->descriptor_lookup_cache(); int index = cache->Lookup(descriptors, sentinel_name); if (index == DescriptorLookupCache::kAbsent) { index = descriptors->Search(sentinel_name); cache->Update(descriptors, sentinel_name, index); } // If the transition already exists, return its descriptor. if (index != DescriptorArray::kNotFound) { PropertyDetails details(descriptors->GetDetails(index)); if (details.type() == ELEMENTS_TRANSITION) { return descriptors->GetValue(index); } else { if (safe_to_add_transition != NULL) { *safe_to_add_transition = false; } } } return NULL; } Map* Map::LookupElementsTransitionMap(ElementsKind elements_kind, bool* safe_to_add_transition) { // Special case: indirect SMI->FAST transition (cf. comment in // AddElementsTransition()). if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && elements_kind == FAST_ELEMENTS) { Map* double_map = this->LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, safe_to_add_transition); if (double_map == NULL) return double_map; return double_map->LookupElementsTransitionMap(FAST_ELEMENTS, safe_to_add_transition); } Object* descriptor_contents = GetDescriptorContents( elements_transition_sentinel_name(), safe_to_add_transition); if (descriptor_contents != NULL) { Map* maybe_transition_map = GetElementsTransitionMapFromDescriptor(descriptor_contents, elements_kind); ASSERT(maybe_transition_map == NULL || maybe_transition_map->IsMap()); return maybe_transition_map; } return NULL; } MaybeObject* Map::AddElementsTransition(ElementsKind elements_kind, Map* transitioned_map) { // The map transition graph should be a tree, therefore the transition // from SMI to FAST elements is not done directly, but by going through // DOUBLE elements first. if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && elements_kind == FAST_ELEMENTS) { bool safe_to_add = true; Map* double_map = this->LookupElementsTransitionMap( FAST_DOUBLE_ELEMENTS, &safe_to_add); // This method is only called when safe_to_add_transition has been found // to be true earlier. ASSERT(safe_to_add); if (double_map == NULL) { MaybeObject* maybe_map = this->CopyDropTransitions(); if (!maybe_map->To(&double_map)) return maybe_map; double_map->set_elements_kind(FAST_DOUBLE_ELEMENTS); MaybeObject* maybe_double_transition = this->AddElementsTransition( FAST_DOUBLE_ELEMENTS, double_map); if (maybe_double_transition->IsFailure()) return maybe_double_transition; } return double_map->AddElementsTransition(FAST_ELEMENTS, transitioned_map); } bool safe_to_add_transition = true; Object* descriptor_contents = GetDescriptorContents( elements_transition_sentinel_name(), &safe_to_add_transition); // This method is only called when safe_to_add_transition has been found // to be true earlier. ASSERT(safe_to_add_transition); MaybeObject* maybe_new_contents = AddElementsTransitionMapToDescriptor(descriptor_contents, transitioned_map); Object* new_contents; if (!maybe_new_contents->ToObject(&new_contents)) { return maybe_new_contents; } ElementsTransitionDescriptor desc(elements_transition_sentinel_name(), new_contents); Object* new_descriptors; MaybeObject* maybe_new_descriptors = instance_descriptors()->CopyInsert(&desc, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return this; } Handle JSObject::GetElementsTransitionMap(Handle object, ElementsKind to_kind) { Isolate* isolate = object->GetIsolate(); CALL_HEAP_FUNCTION(isolate, object->GetElementsTransitionMap(isolate, to_kind), Map); } MaybeObject* JSObject::GetElementsTransitionMapSlow(ElementsKind to_kind) { Map* current_map = map(); ElementsKind from_kind = current_map->elements_kind(); if (from_kind == to_kind) return current_map; // Only objects with FastProperties can have DescriptorArrays and can track // element-related maps. Also don't add descriptors to maps that are shared. bool safe_to_add_transition = HasFastProperties() && !current_map->IsUndefined() && !current_map->is_shared(); // Prevent long chains of DICTIONARY -> FAST_ELEMENTS maps caused by objects // with elements that switch back and forth between dictionary and fast // element mode. if (from_kind == DICTIONARY_ELEMENTS && to_kind == FAST_ELEMENTS) { safe_to_add_transition = false; } if (safe_to_add_transition) { // It's only safe to manipulate the descriptor array if it would be // safe to add a transition. Map* maybe_transition_map = current_map->LookupElementsTransitionMap( to_kind, &safe_to_add_transition); if (maybe_transition_map != NULL) { return maybe_transition_map; } } Map* new_map = NULL; // No transition to an existing map for the given ElementsKind. Make a new // one. { MaybeObject* maybe_map = current_map->CopyDropTransitions(); if (!maybe_map->To(&new_map)) return maybe_map; } new_map->set_elements_kind(to_kind); // Only remember the map transition if the object's map is NOT equal to the // global object_function's map and there is not an already existing // non-matching element transition. Context* global_context = GetIsolate()->context()->global_context(); bool allow_map_transition = safe_to_add_transition && (global_context->object_function()->map() != map()); if (allow_map_transition) { MaybeObject* maybe_transition = current_map->AddElementsTransition(to_kind, new_map); if (maybe_transition->IsFailure()) return maybe_transition; } return new_map; } void JSObject::LocalLookupRealNamedProperty(String* name, LookupResult* result) { if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); // A GlobalProxy's prototype should always be a proper JSObject. return JSObject::cast(proto)->LocalLookupRealNamedProperty(name, result); } if (HasFastProperties()) { LookupInDescriptor(name, result); if (result->IsFound()) { // A property, a map transition or a null descriptor was found. // We return all of these result types because // LocalLookupRealNamedProperty is used when setting properties // where map transitions and null descriptors are handled. ASSERT(result->holder() == this && result->type() != NORMAL); // Disallow caching for uninitialized constants. These can only // occur as fields. if (result->IsReadOnly() && result->type() == FIELD && FastPropertyAt(result->GetFieldIndex())->IsTheHole()) { result->DisallowCaching(); } return; } } else { int entry = property_dictionary()->FindEntry(name); if (entry != StringDictionary::kNotFound) { Object* value = property_dictionary()->ValueAt(entry); if (IsGlobalObject()) { PropertyDetails d = property_dictionary()->DetailsAt(entry); if (d.IsDeleted()) { result->NotFound(); return; } value = JSGlobalPropertyCell::cast(value)->value(); } // Make sure to disallow caching for uninitialized constants // found in the dictionary-mode objects. if (value->IsTheHole()) result->DisallowCaching(); result->DictionaryResult(this, entry); return; } } result->NotFound(); } void JSObject::LookupRealNamedProperty(String* name, LookupResult* result) { LocalLookupRealNamedProperty(name, result); if (result->IsProperty()) return; LookupRealNamedPropertyInPrototypes(name, result); } void JSObject::LookupRealNamedPropertyInPrototypes(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = JSObject::cast(pt)->GetPrototype()) { JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); if (result->IsProperty() && (result->type() != INTERCEPTOR)) return; } result->NotFound(); } // We only need to deal with CALLBACKS and INTERCEPTORS MaybeObject* JSObject::SetPropertyWithFailedAccessCheck( LookupResult* result, String* name, Object* value, bool check_prototype, StrictModeFlag strict_mode) { if (check_prototype && !result->IsProperty()) { LookupCallbackSetterInPrototypes(name, result); } if (result->IsProperty()) { if (!result->IsReadOnly()) { switch (result->type()) { case CALLBACKS: { Object* obj = result->GetCallbackObject(); if (obj->IsAccessorInfo()) { AccessorInfo* info = AccessorInfo::cast(obj); if (info->all_can_write()) { return SetPropertyWithCallback(result->GetCallbackObject(), name, value, result->holder(), strict_mode); } } break; } case INTERCEPTOR: { // Try lookup real named properties. Note that only property can be // set is callbacks marked as ALL_CAN_WRITE on the prototype chain. LookupResult r(GetIsolate()); LookupRealNamedProperty(name, &r); if (r.IsProperty()) { return SetPropertyWithFailedAccessCheck(&r, name, value, check_prototype, strict_mode); } break; } default: { break; } } } } Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle value_handle(value); isolate->ReportFailedAccessCheck(this, v8::ACCESS_SET); return *value_handle; } MaybeObject* JSReceiver::SetProperty(LookupResult* result, String* key, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { if (result->IsFound() && result->type() == HANDLER) { return result->proxy()->SetPropertyWithHandler( key, value, attributes, strict_mode); } else { return JSObject::cast(this)->SetPropertyForResult( result, key, value, attributes, strict_mode); } } bool JSProxy::HasPropertyWithHandler(String* name_raw) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "has", isolate->derived_has_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); return result->ToBoolean()->IsTrue(); } MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler( String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle value(value_raw); Handle args[] = { receiver, name, value }; CallTrap("set", isolate->derived_set_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); return *value; } MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandlerIfDefiningSetter( String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode, bool* found) { *found = true; // except where defined otherwise... Isolate* isolate = GetHeap()->isolate(); Handle proxy(this); Handle handler(this->handler()); // Trap might morph proxy. Handle name(name_raw); Handle value(value_raw); Handle args[] = { name }; Handle result = proxy->CallTrap( "getPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); if (!result->IsUndefined()) { // The proxy handler cares about this property. // Check whether it is virtualized as an accessor. // Emulate [[GetProperty]] semantics for proxies. bool has_pending_exception; Handle argv[] = { result }; Handle desc = Execution::Call(isolate->to_complete_property_descriptor(), result, ARRAY_SIZE(argv), argv, &has_pending_exception); if (has_pending_exception) return Failure::Exception(); Handle conf_name = isolate->factory()->LookupAsciiSymbol("configurable_"); Handle configurable(v8::internal::GetProperty(desc, conf_name)); ASSERT(!isolate->has_pending_exception()); if (configurable->IsFalse()) { Handle trap = isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); Handle args[] = { handler, trap, name }; Handle error = isolate->factory()->NewTypeError( "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); } ASSERT(configurable->IsTrue()); // Check for AccessorDescriptor. Handle set_name = isolate->factory()->LookupAsciiSymbol("set_"); Handle setter(v8::internal::GetProperty(desc, set_name)); ASSERT(!isolate->has_pending_exception()); if (!setter->IsUndefined()) { // We have a setter -- invoke it. // TODO(rossberg): nicer would be to cast to some JSCallable here... return proxy->SetPropertyWithDefinedSetter( JSReceiver::cast(*setter), *value); } else { Handle get_name = isolate->factory()->LookupAsciiSymbol("get_"); Handle getter(v8::internal::GetProperty(desc, get_name)); ASSERT(!isolate->has_pending_exception()); if (!getter->IsUndefined()) { // We have a getter but no setter -- the property may not be // written. In strict mode, throw an error. if (strict_mode == kNonStrictMode) return *value; Handle args[] = { name, proxy }; Handle error = isolate->factory()->NewTypeError( "no_setter_in_callback", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); } } // Fall-through. } // The proxy does not define the property as an accessor. *found = false; return *value; } MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( String* name_raw, DeleteMode mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "delete", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); Object* bool_result = result->ToBoolean(); if (mode == STRICT_DELETION && bool_result == GetHeap()->false_value()) { Handle trap_name = isolate->factory()->LookupAsciiSymbol("delete"); Handle args[] = { Handle(handler()), trap_name }; Handle error = isolate->factory()->NewTypeError( "handler_failed", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return Failure::Exception(); } return bool_result; } MUST_USE_RESULT MaybeObject* JSProxy::DeleteElementWithHandler( uint32_t index, DeleteMode mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle name = isolate->factory()->Uint32ToString(index); return JSProxy::DeletePropertyWithHandler(*name, mode); } MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler( JSReceiver* receiver_raw, String* name_raw) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle proxy(this); Handle handler(this->handler()); // Trap might morph proxy. Handle receiver(receiver_raw); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "getPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return NONE; if (result->IsUndefined()) return ABSENT; bool has_pending_exception; Handle argv[] = { result }; Handle desc = Execution::Call(isolate->to_complete_property_descriptor(), result, ARRAY_SIZE(argv), argv, &has_pending_exception); if (has_pending_exception) return NONE; // Convert result to PropertyAttributes. Handle enum_n = isolate->factory()->LookupAsciiSymbol("enumerable"); Handle enumerable(v8::internal::GetProperty(desc, enum_n)); if (isolate->has_pending_exception()) return NONE; Handle conf_n = isolate->factory()->LookupAsciiSymbol("configurable"); Handle configurable(v8::internal::GetProperty(desc, conf_n)); if (isolate->has_pending_exception()) return NONE; Handle writ_n = isolate->factory()->LookupAsciiSymbol("writable"); Handle writable(v8::internal::GetProperty(desc, writ_n)); if (isolate->has_pending_exception()) return NONE; if (configurable->IsFalse()) { Handle trap = isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); Handle args[] = { handler, trap, name }; Handle error = isolate->factory()->NewTypeError( "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return NONE; } int attributes = NONE; if (enumerable->ToBoolean()->IsFalse()) attributes |= DONT_ENUM; if (configurable->ToBoolean()->IsFalse()) attributes |= DONT_DELETE; if (writable->ToBoolean()->IsFalse()) attributes |= READ_ONLY; return static_cast(attributes); } MUST_USE_RESULT PropertyAttributes JSProxy::GetElementAttributeWithHandler( JSReceiver* receiver, uint32_t index) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle name = isolate->factory()->Uint32ToString(index); return GetPropertyAttributeWithHandler(receiver, *name); } void JSProxy::Fix() { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle self(this); // Save identity hash. MaybeObject* maybe_hash = GetIdentityHash(OMIT_CREATION); if (IsJSFunctionProxy()) { isolate->factory()->BecomeJSFunction(self); // Code will be set on the JavaScript side. } else { isolate->factory()->BecomeJSObject(self); } ASSERT(self->IsJSObject()); // Inherit identity, if it was present. Object* hash; if (maybe_hash->To(&hash) && hash->IsSmi()) { Handle new_self(JSObject::cast(*self)); isolate->factory()->SetIdentityHash(new_self, hash); } } MUST_USE_RESULT Handle JSProxy::CallTrap(const char* name, Handle derived, int argc, Handle argv[]) { Isolate* isolate = GetIsolate(); Handle handler(this->handler()); Handle trap_name = isolate->factory()->LookupAsciiSymbol(name); Handle trap(v8::internal::GetProperty(handler, trap_name)); if (isolate->has_pending_exception()) return trap; if (trap->IsUndefined()) { if (derived.is_null()) { Handle args[] = { handler, trap_name }; Handle error = isolate->factory()->NewTypeError( "handler_trap_missing", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return Handle(); } trap = Handle(derived); } bool threw; return Execution::Call(trap, handler, argc, argv, &threw); } MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); // Make sure that the top context does not change when doing callbacks or // interceptor calls. AssertNoContextChange ncc; // Optimization for 2-byte strings often used as keys in a decompression // dictionary. We make these short keys into symbols to avoid constantly // reallocating them. if (!name->IsSymbol() && name->length() <= 2) { Object* symbol_version; { MaybeObject* maybe_symbol_version = heap->LookupSymbol(name); if (maybe_symbol_version->ToObject(&symbol_version)) { name = String::cast(symbol_version); } } } // Check access rights if needed. if (IsAccessCheckNeeded()) { if (!heap->isolate()->MayNamedAccess(this, name, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck( result, name, value, true, strict_mode); } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetPropertyForResult( result, name, value, attributes, strict_mode); } if (!result->IsProperty() && !IsJSContextExtensionObject()) { bool found = false; MaybeObject* result_object; result_object = SetPropertyWithCallbackSetterInPrototypes(name, value, attributes, &found, strict_mode); if (found) return result_object; } // At this point, no GC should have happened, as this would invalidate // 'result', which we cannot handlify! if (!result->IsFound()) { // Neither properties nor transitions found. return AddProperty(name, value, attributes, strict_mode); } if (result->IsReadOnly() && result->IsProperty()) { if (strict_mode == kStrictMode) { Handle self(this); Handle hname(name); Handle args[] = { hname, self }; return heap->isolate()->Throw(*heap->isolate()->factory()->NewTypeError( "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)))); } else { return value; } } // This is a real property that is not read-only, or it is a // transition or null descriptor and there are no setters in the prototypes. switch (result->type()) { case NORMAL: return SetNormalizedProperty(result, value); case FIELD: return FastPropertyAtPut(result->GetFieldIndex(), value); case MAP_TRANSITION: if (attributes == result->GetAttributes()) { // Only use map transition if the attributes match. return AddFastPropertyUsingMap(result->GetTransitionMap(), name, value); } return ConvertDescriptorToField(name, value, attributes); case CONSTANT_FUNCTION: // Only replace the function if necessary. if (value == result->GetConstantFunction()) return value; // Preserve the attributes of this existing property. attributes = result->GetAttributes(); return ConvertDescriptorToField(name, value, attributes); case CALLBACKS: return SetPropertyWithCallback(result->GetCallbackObject(), name, value, result->holder(), strict_mode); case INTERCEPTOR: return SetPropertyWithInterceptor(name, value, attributes, strict_mode); case CONSTANT_TRANSITION: { // If the same constant function is being added we can simply // transition to the target map. Map* target_map = result->GetTransitionMap(); DescriptorArray* target_descriptors = target_map->instance_descriptors(); int number = target_descriptors->SearchWithCache(name); ASSERT(number != DescriptorArray::kNotFound); ASSERT(target_descriptors->GetType(number) == CONSTANT_FUNCTION); JSFunction* function = JSFunction::cast(target_descriptors->GetValue(number)); if (value == function) { set_map(target_map); return value; } // Otherwise, replace with a MAP_TRANSITION to a new map with a // FIELD, even if the value is a constant function. return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); } case NULL_DESCRIPTOR: case ELEMENTS_TRANSITION: return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case HANDLER: UNREACHABLE(); return value; } UNREACHABLE(); // keep the compiler happy return value; } // Set a real local property, even if it is READ_ONLY. If the property is not // present, add it with attributes NONE. This code is an exact clone of // SetProperty, with the check for IsReadOnly and the check for a // callback setter removed. The two lines looking up the LookupResult // result are also added. If one of the functions is changed, the other // should be. // Note that this method cannot be used to set the prototype of a function // because ConvertDescriptorToField() which is called in "case CALLBACKS:" // doesn't handle function prototypes correctly. Handle JSObject::SetLocalPropertyIgnoreAttributes( Handle object, Handle key, Handle value, PropertyAttributes attributes) { CALL_HEAP_FUNCTION( object->GetIsolate(), object->SetLocalPropertyIgnoreAttributes(*key, *value, attributes), Object); } MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( String* name, Object* value, PropertyAttributes attributes) { // Make sure that the top context does not change when doing callbacks or // interceptor calls. AssertNoContextChange ncc; Isolate* isolate = GetIsolate(); LookupResult result(isolate); LocalLookup(name, &result); // Check access rights if needed. if (IsAccessCheckNeeded()) { if (!isolate->MayNamedAccess(this, name, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck(&result, name, value, false, kNonStrictMode); } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetLocalPropertyIgnoreAttributes( name, value, attributes); } // Check for accessor in prototype chain removed here in clone. if (!result.IsFound()) { // Neither properties nor transitions found. return AddProperty(name, value, attributes, kNonStrictMode); } PropertyDetails details = PropertyDetails(attributes, NORMAL); // Check of IsReadOnly removed from here in clone. switch (result.type()) { case NORMAL: return SetNormalizedProperty(name, value, details); case FIELD: return FastPropertyAtPut(result.GetFieldIndex(), value); case MAP_TRANSITION: if (attributes == result.GetAttributes()) { // Only use map transition if the attributes match. return AddFastPropertyUsingMap(result.GetTransitionMap(), name, value); } return ConvertDescriptorToField(name, value, attributes); case CONSTANT_FUNCTION: // Only replace the function if necessary. if (value == result.GetConstantFunction()) return value; // Preserve the attributes of this existing property. attributes = result.GetAttributes(); return ConvertDescriptorToField(name, value, attributes); case CALLBACKS: case INTERCEPTOR: // Override callback in clone return ConvertDescriptorToField(name, value, attributes); case CONSTANT_TRANSITION: // Replace with a MAP_TRANSITION to a new map with a FIELD, even // if the value is a function. return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case NULL_DESCRIPTOR: case ELEMENTS_TRANSITION: return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case HANDLER: UNREACHABLE(); return value; } UNREACHABLE(); // keep the compiler happy return value; } PropertyAttributes JSObject::GetPropertyAttributePostInterceptor( JSObject* receiver, String* name, bool continue_search) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsProperty()) return result.GetAttributes(); if (continue_search) { // Continue searching via the prototype chain. Object* pt = GetPrototype(); if (!pt->IsNull()) { return JSObject::cast(pt)-> GetPropertyAttributeWithReceiver(receiver, name); } } return ABSENT; } PropertyAttributes JSObject::GetPropertyAttributeWithInterceptor( JSObject* receiver, String* name, bool continue_search) { Isolate* isolate = GetIsolate(); // Make sure that the top context does not change when doing // callbacks or interceptor calls. AssertNoContextChange ncc; HandleScope scope(isolate); Handle interceptor(GetNamedInterceptor()); Handle receiver_handle(receiver); Handle holder_handle(this); Handle name_handle(name); CustomArguments args(isolate, interceptor->data(), receiver, this); v8::AccessorInfo info(args.end()); if (!interceptor->query()->IsUndefined()) { v8::NamedPropertyQuery query = v8::ToCData(interceptor->query()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-has", *holder_handle, name)); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = query(v8::Utils::ToLocal(name_handle), info); } if (!result.IsEmpty()) { ASSERT(result->IsInt32()); return static_cast(result->Int32Value()); } } else if (!interceptor->getter()->IsUndefined()) { v8::NamedPropertyGetter getter = v8::ToCData(interceptor->getter()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-get-has", this, name)); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = getter(v8::Utils::ToLocal(name_handle), info); } if (!result.IsEmpty()) return DONT_ENUM; } return holder_handle->GetPropertyAttributePostInterceptor(*receiver_handle, *name_handle, continue_search); } PropertyAttributes JSReceiver::GetPropertyAttributeWithReceiver( JSReceiver* receiver, String* key) { uint32_t index = 0; if (IsJSObject() && key->AsArrayIndex(&index)) { return JSObject::cast(this)->HasElementWithReceiver(receiver, index) ? NONE : ABSENT; } // Named property. LookupResult result(GetIsolate()); Lookup(key, &result); return GetPropertyAttribute(receiver, &result, key, true); } PropertyAttributes JSReceiver::GetPropertyAttribute(JSReceiver* receiver, LookupResult* result, String* name, bool continue_search) { // Check access rights if needed. if (IsAccessCheckNeeded()) { JSObject* this_obj = JSObject::cast(this); Heap* heap = GetHeap(); if (!heap->isolate()->MayNamedAccess(this_obj, name, v8::ACCESS_HAS)) { return this_obj->GetPropertyAttributeWithFailedAccessCheck( receiver, result, name, continue_search); } } if (result->IsProperty()) { switch (result->type()) { case NORMAL: // fall through case FIELD: case CONSTANT_FUNCTION: case CALLBACKS: return result->GetAttributes(); case HANDLER: { return JSProxy::cast(result->proxy())->GetPropertyAttributeWithHandler( receiver, name); } case INTERCEPTOR: return result->holder()->GetPropertyAttributeWithInterceptor( JSObject::cast(receiver), name, continue_search); default: UNREACHABLE(); } } return ABSENT; } PropertyAttributes JSReceiver::GetLocalPropertyAttribute(String* name) { // Check whether the name is an array index. uint32_t index = 0; if (IsJSObject() && name->AsArrayIndex(&index)) { if (JSObject::cast(this)->HasLocalElement(index)) return NONE; return ABSENT; } // Named property. LookupResult result(GetIsolate()); LocalLookup(name, &result); return GetPropertyAttribute(this, &result, name, false); } MaybeObject* NormalizedMapCache::Get(JSObject* obj, PropertyNormalizationMode mode) { Isolate* isolate = obj->GetIsolate(); Map* fast = obj->map(); int index = fast->Hash() % kEntries; Object* result = get(index); if (result->IsMap() && Map::cast(result)->EquivalentToForNormalization(fast, mode)) { #ifdef DEBUG if (FLAG_verify_heap) { Map::cast(result)->SharedMapVerify(); } if (FLAG_enable_slow_asserts) { // The cached map should match newly created normalized map bit-by-bit. Object* fresh; { MaybeObject* maybe_fresh = fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); if (maybe_fresh->ToObject(&fresh)) { ASSERT(memcmp(Map::cast(fresh)->address(), Map::cast(result)->address(), Map::kSize) == 0); } } } #endif return result; } { MaybeObject* maybe_result = fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); if (!maybe_result->ToObject(&result)) return maybe_result; } set(index, result); isolate->counters()->normalized_maps()->Increment(); return result; } void NormalizedMapCache::Clear() { int entries = length(); for (int i = 0; i != entries; i++) { set_undefined(i); } } void JSObject::UpdateMapCodeCache(Handle object, Handle name, Handle code) { Isolate* isolate = object->GetIsolate(); CALL_HEAP_FUNCTION_VOID(isolate, object->UpdateMapCodeCache(*name, *code)); } MaybeObject* JSObject::UpdateMapCodeCache(String* name, Code* code) { if (map()->is_shared()) { // Fast case maps are never marked as shared. ASSERT(!HasFastProperties()); // Replace the map with an identical copy that can be safely modified. Object* obj; { MaybeObject* maybe_obj = map()->CopyNormalized(KEEP_INOBJECT_PROPERTIES, UNIQUE_NORMALIZED_MAP); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } GetIsolate()->counters()->normalized_maps()->Increment(); set_map(Map::cast(obj)); } return map()->UpdateCodeCache(name, code); } void JSObject::NormalizeProperties(Handle object, PropertyNormalizationMode mode, int expected_additional_properties) { CALL_HEAP_FUNCTION_VOID(object->GetIsolate(), object->NormalizeProperties( mode, expected_additional_properties)); } MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, int expected_additional_properties) { if (!HasFastProperties()) return this; // The global object is always normalized. ASSERT(!IsGlobalObject()); // JSGlobalProxy must never be normalized ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); // Allocate new content. int property_count = map_of_this->NumberOfDescribedProperties(); if (expected_additional_properties > 0) { property_count += expected_additional_properties; } else { property_count += 2; // Make space for two more properties. } StringDictionary* dictionary; { MaybeObject* maybe_dictionary = StringDictionary::Allocate(property_count); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } DescriptorArray* descs = map_of_this->instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); switch (details.type()) { case CONSTANT_FUNCTION: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = descs->GetConstantFunction(i); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case FIELD: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = FastPropertyAt(descs->GetFieldIndex(i)); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case CALLBACKS: { if (!descs->IsProperty(i)) break; Object* value = descs->GetCallbacksObject(i); if (value->IsAccessorPair()) { MaybeObject* maybe_copy = AccessorPair::cast(value)->CopyWithoutTransitions(); if (!maybe_copy->To(&value)) return maybe_copy; } MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case MAP_TRANSITION: case CONSTANT_TRANSITION: case NULL_DESCRIPTOR: case INTERCEPTOR: case ELEMENTS_TRANSITION: break; case HANDLER: case NORMAL: UNREACHABLE(); break; } } Heap* current_heap = GetHeap(); // Copy the next enumeration index from instance descriptor. int index = map_of_this->instance_descriptors()->NextEnumerationIndex(); dictionary->SetNextEnumerationIndex(index); Map* new_map; { MaybeObject* maybe_map = current_heap->isolate()->context()->global_context()-> normalized_map_cache()->Get(this, mode); if (!maybe_map->To(&new_map)) return maybe_map; } // We have now successfully allocated all the necessary objects. // Changes can now be made with the guarantee that all of them take effect. // Resize the object in the heap if necessary. int new_instance_size = new_map->instance_size(); int instance_size_delta = map_of_this->instance_size() - new_instance_size; ASSERT(instance_size_delta >= 0); current_heap->CreateFillerObjectAt(this->address() + new_instance_size, instance_size_delta); if (Marking::IsBlack(Marking::MarkBitFrom(this))) { MemoryChunk::IncrementLiveBytesFromMutator(this->address(), -instance_size_delta); } set_map(new_map); new_map->clear_instance_descriptors(); set_properties(dictionary); current_heap->isolate()->counters()->props_to_dictionary()->Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object properties have been normalized:\n"); Print(); } #endif return this; } void JSObject::TransformToFastProperties(Handle object, int unused_property_fields) { CALL_HEAP_FUNCTION_VOID( object->GetIsolate(), object->TransformToFastProperties(unused_property_fields)); } MaybeObject* JSObject::TransformToFastProperties(int unused_property_fields) { if (HasFastProperties()) return this; ASSERT(!IsGlobalObject()); return property_dictionary()-> TransformPropertiesToFastFor(this, unused_property_fields); } Handle JSObject::NormalizeElements( Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->NormalizeElements(), SeededNumberDictionary); } MaybeObject* JSObject::NormalizeElements() { ASSERT(!HasExternalArrayElements()); // Find the backing store. FixedArrayBase* array = FixedArrayBase::cast(elements()); Map* old_map = array->map(); bool is_arguments = (old_map == old_map->GetHeap()->non_strict_arguments_elements_map()); if (is_arguments) { array = FixedArrayBase::cast(FixedArray::cast(array)->get(1)); } if (array->IsDictionary()) return array; ASSERT(HasFastElements() || HasFastSmiOnlyElements() || HasFastDoubleElements() || HasFastArgumentsElements()); // Compute the effective length and allocate a new backing store. int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : array->length(); int old_capacity = 0; int used_elements = 0; GetElementsCapacityAndUsage(&old_capacity, &used_elements); SeededNumberDictionary* dictionary = NULL; { Object* object; MaybeObject* maybe = SeededNumberDictionary::Allocate(used_elements); if (!maybe->ToObject(&object)) return maybe; dictionary = SeededNumberDictionary::cast(object); } // Copy the elements to the new backing store. bool has_double_elements = array->IsFixedDoubleArray(); for (int i = 0; i < length; i++) { Object* value = NULL; if (has_double_elements) { FixedDoubleArray* double_array = FixedDoubleArray::cast(array); if (double_array->is_the_hole(i)) { value = GetIsolate()->heap()->the_hole_value(); } else { // Objects must be allocated in the old object space, since the // overall number of HeapNumbers needed for the conversion might // exceed the capacity of new space, and we would fail repeatedly // trying to convert the FixedDoubleArray. MaybeObject* maybe_value_object = GetHeap()->AllocateHeapNumber(double_array->get_scalar(i), TENURED); if (!maybe_value_object->ToObject(&value)) return maybe_value_object; } } else { ASSERT(old_map->has_fast_elements() || old_map->has_fast_smi_only_elements()); value = FixedArray::cast(array)->get(i); } PropertyDetails details = PropertyDetails(NONE, NORMAL); if (!value->IsTheHole()) { Object* result; MaybeObject* maybe_result = dictionary->AddNumberEntry(i, value, details); if (!maybe_result->ToObject(&result)) return maybe_result; dictionary = SeededNumberDictionary::cast(result); } } // Switch to using the dictionary as the backing storage for elements. if (is_arguments) { FixedArray::cast(elements())->set(1, dictionary); } else { // Set the new map first to satify the elements type assert in // set_elements(). Object* new_map; MaybeObject* maybe = GetElementsTransitionMap(GetIsolate(), DICTIONARY_ELEMENTS); if (!maybe->ToObject(&new_map)) return maybe; set_map(Map::cast(new_map)); set_elements(dictionary); } old_map->GetHeap()->isolate()->counters()->elements_to_dictionary()-> Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object elements have been normalized:\n"); Print(); } #endif ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); return dictionary; } Smi* JSReceiver::GenerateIdentityHash() { Isolate* isolate = GetIsolate(); int hash_value; int attempts = 0; do { // Generate a random 32-bit hash value but limit range to fit // within a smi. hash_value = V8::RandomPrivate(isolate) & Smi::kMaxValue; attempts++; } while (hash_value == 0 && attempts < 30); hash_value = hash_value != 0 ? hash_value : 1; // never return 0 return Smi::FromInt(hash_value); } MaybeObject* JSObject::SetIdentityHash(Object* hash, CreationFlag flag) { MaybeObject* maybe = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (maybe->IsFailure()) return maybe; return this; } int JSObject::GetIdentityHash(Handle obj) { CALL_AND_RETRY(obj->GetIsolate(), obj->GetIdentityHash(ALLOW_CREATION), return Smi::cast(__object__)->value(), return 0); } MaybeObject* JSObject::GetIdentityHash(CreationFlag flag) { Object* stored_value = GetHiddenProperty(GetHeap()->identity_hash_symbol()); if (stored_value->IsSmi()) return stored_value; // Do not generate permanent identity hash code if not requested. if (flag == OMIT_CREATION) return GetHeap()->undefined_value(); Smi* hash = GenerateIdentityHash(); MaybeObject* result = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (result->IsFailure()) return result; if (result->ToObjectUnchecked()->IsUndefined()) { // Trying to get hash of detached proxy. return Smi::FromInt(0); } return hash; } MaybeObject* JSProxy::GetIdentityHash(CreationFlag flag) { Object* hash = this->hash(); if (!hash->IsSmi() && flag == ALLOW_CREATION) { hash = GenerateIdentityHash(); set_hash(hash); } return hash; } Object* JSObject::GetHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->GetHiddenProperty(key); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) { return GetHeap()->undefined_value(); } StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) return GetHeap()->undefined_value(); return dictionary->ValueAt(entry); } Handle JSObject::SetHiddenProperty(Handle obj, Handle key, Handle value) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->SetHiddenProperty(*key, *value), Object); } MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->SetHiddenProperty(key, value); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(true); StringDictionary* dictionary; if (!hidden_lookup->To(&dictionary)) return hidden_lookup; // If it was found, check if the key is already in the dictionary. int entry = dictionary->FindEntry(key); if (entry != StringDictionary::kNotFound) { // If key was found, just update the value. dictionary->ValueAtPut(entry, value); return this; } // Key was not already in the dictionary, so add the entry. MaybeObject* insert_result = dictionary->Add(key, value, PropertyDetails(NONE, NORMAL)); StringDictionary* new_dict; if (!insert_result->To(&new_dict)) return insert_result; if (new_dict != dictionary) { // If adding the key expanded the dictionary (i.e., Add returned a new // dictionary), store it back to the object. MaybeObject* store_result = SetHiddenPropertiesDictionary(new_dict); if (store_result->IsFailure()) return store_result; } // Return this to mark success. return this; } void JSObject::DeleteHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return immediately. if (proxy_parent->IsNull()) return; ASSERT(proxy_parent->IsJSGlobalObject()); JSObject::cast(proxy_parent)->DeleteHiddenProperty(key); return; } MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) return; StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) { // Key wasn't in dictionary. Deletion is a success. return; } // Key was in the dictionary. Remove it. dictionary->DeleteProperty(entry, JSReceiver::FORCE_DELETION); } bool JSObject::HasHiddenProperties() { return GetPropertyAttributePostInterceptor(this, GetHeap()->hidden_symbol(), false) != ABSENT; } MaybeObject* JSObject::GetHiddenPropertiesDictionary(bool create_if_absent) { ASSERT(!IsJSGlobalProxy()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { Object* hidden_store = this->FastPropertyAt(descriptors->GetFieldIndex(0)); return StringDictionary::cast(hidden_store); } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } else { PropertyAttributes attributes; // You can't install a getter on a property indexed by the hidden symbol, // so we can be sure that GetLocalPropertyPostInterceptor returns a real // object. Object* lookup = GetLocalPropertyPostInterceptor(this, GetHeap()->hidden_symbol(), &attributes)->ToObjectUnchecked(); if (!lookup->IsUndefined()) { return StringDictionary::cast(lookup); } } if (!create_if_absent) return GetHeap()->undefined_value(); const int kInitialSize = 5; MaybeObject* dict_alloc = StringDictionary::Allocate(kInitialSize); StringDictionary* dictionary; if (!dict_alloc->To(&dictionary)) return dict_alloc; MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return dictionary; } MaybeObject* JSObject::SetHiddenPropertiesDictionary( StringDictionary* dictionary) { ASSERT(!IsJSGlobalProxy()); ASSERT(HasHiddenProperties()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { this->FastPropertyAtPut(descriptors->GetFieldIndex(0), dictionary); return this; } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return this; } MaybeObject* JSObject::DeletePropertyPostInterceptor(String* name, DeleteMode mode) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (!result.IsProperty()) return GetHeap()->true_value(); // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return DeleteNormalizedProperty(name, mode); } MaybeObject* JSObject::DeletePropertyWithInterceptor(String* name) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle interceptor(GetNamedInterceptor()); Handle name_handle(name); Handle this_handle(this); if (!interceptor->deleter()->IsUndefined()) { v8::NamedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-delete", *this_handle, name)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(v8::Utils::ToLocal(name_handle), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } } MaybeObject* raw_result = this_handle->DeletePropertyPostInterceptor(*name_handle, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } MaybeObject* JSObject::DeleteElementWithInterceptor(uint32_t index) { Isolate* isolate = GetIsolate(); Heap* heap = isolate->heap(); // Make sure that the top context does not change when doing // callbacks or interceptor calls. AssertNoContextChange ncc; HandleScope scope(isolate); Handle interceptor(GetIndexedInterceptor()); if (interceptor->deleter()->IsUndefined()) return heap->false_value(); v8::IndexedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); Handle this_handle(this); LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-delete", this, index)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(index, info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } MaybeObject* raw_result = this_handle->GetElementsAccessor()->Delete( *this_handle, index, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } Handle JSObject::DeleteElement(Handle obj, uint32_t index) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteElement(index, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { Isolate* isolate = GetIsolate(); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayIndexedAccess(this, index, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteElement(index, mode); } if (HasIndexedInterceptor()) { // Skip interceptor if forcing deletion. if (mode != FORCE_DELETION) { return DeleteElementWithInterceptor(index); } mode = JSReceiver::FORCE_DELETION; } return GetElementsAccessor()->Delete(this, index, mode); } Handle JSObject::DeleteProperty(Handle obj, Handle prop) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteProperty(*prop, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) { Isolate* isolate = GetIsolate(); // ECMA-262, 3rd, 8.6.2.5 ASSERT(name->IsString()); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, name, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteProperty(name, mode); } uint32_t index = 0; if (name->AsArrayIndex(&index)) { return DeleteElement(index, mode); } else { LookupResult result(isolate); LocalLookup(name, &result); if (!result.IsProperty()) return isolate->heap()->true_value(); // Ignore attributes if forcing a deletion. if (result.IsDontDelete() && mode != FORCE_DELETION) { if (mode == STRICT_DELETION) { // Deleting a non-configurable property in strict mode. HandleScope scope(isolate); Handle args[2] = { Handle(name), Handle(this) }; return isolate->Throw(*isolate->factory()->NewTypeError( "strict_delete_property", HandleVector(args, 2))); } return isolate->heap()->false_value(); } // Check for interceptor. if (result.type() == INTERCEPTOR) { // Skip interceptor if forcing a deletion. if (mode == FORCE_DELETION) { return DeletePropertyPostInterceptor(name, mode); } return DeletePropertyWithInterceptor(name); } // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } // Make sure the properties are normalized before removing the entry. return DeleteNormalizedProperty(name, mode); } } MaybeObject* JSReceiver::DeleteElement(uint32_t index, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeleteElementWithHandler(index, mode); } return JSObject::cast(this)->DeleteElement(index, mode); } MaybeObject* JSReceiver::DeleteProperty(String* name, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeletePropertyWithHandler(name, mode); } return JSObject::cast(this)->DeleteProperty(name, mode); } bool JSObject::ReferencesObjectFromElements(FixedArray* elements, ElementsKind kind, Object* object) { ASSERT(kind == FAST_ELEMENTS || kind == DICTIONARY_ELEMENTS); if (kind == FAST_ELEMENTS) { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : elements->length(); for (int i = 0; i < length; ++i) { Object* element = elements->get(i); if (!element->IsTheHole() && element == object) return true; } } else { Object* key = SeededNumberDictionary::cast(elements)->SlowReverseLookup(object); if (!key->IsUndefined()) return true; } return false; } // Check whether this object references another object. bool JSObject::ReferencesObject(Object* obj) { Map* map_of_this = map(); Heap* heap = GetHeap(); AssertNoAllocation no_alloc; // Is the object the constructor for this object? if (map_of_this->constructor() == obj) { return true; } // Is the object the prototype for this object? if (map_of_this->prototype() == obj) { return true; } // Check if the object is among the named properties. Object* key = SlowReverseLookup(obj); if (!key->IsUndefined()) { return true; } // Check if the object is among the indexed properties. ElementsKind kind = GetElementsKind(); switch (kind) { case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: case FAST_DOUBLE_ELEMENTS: // Raw pixels and external arrays do not reference other // objects. break; case FAST_SMI_ONLY_ELEMENTS: break; case FAST_ELEMENTS: case DICTIONARY_ELEMENTS: { FixedArray* elements = FixedArray::cast(this->elements()); if (ReferencesObjectFromElements(elements, kind, obj)) return true; break; } case NON_STRICT_ARGUMENTS_ELEMENTS: { FixedArray* parameter_map = FixedArray::cast(elements()); // Check the mapped parameters. int length = parameter_map->length(); for (int i = 2; i < length; ++i) { Object* value = parameter_map->get(i); if (!value->IsTheHole() && value == obj) return true; } // Check the arguments. FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); kind = arguments->IsDictionary() ? DICTIONARY_ELEMENTS : FAST_ELEMENTS; if (ReferencesObjectFromElements(arguments, kind, obj)) return true; break; } } // For functions check the context. if (IsJSFunction()) { // Get the constructor function for arguments array. JSObject* arguments_boilerplate = heap->isolate()->context()->global_context()-> arguments_boilerplate(); JSFunction* arguments_function = JSFunction::cast(arguments_boilerplate->map()->constructor()); // Get the context and don't check if it is the global context. JSFunction* f = JSFunction::cast(this); Context* context = f->context(); if (context->IsGlobalContext()) { return false; } // Check the non-special context slots. for (int i = Context::MIN_CONTEXT_SLOTS; i < context->length(); i++) { // Only check JS objects. if (context->get(i)->IsJSObject()) { JSObject* ctxobj = JSObject::cast(context->get(i)); // If it is an arguments array check the content. if (ctxobj->map()->constructor() == arguments_function) { if (ctxobj->ReferencesObject(obj)) { return true; } } else if (ctxobj == obj) { return true; } } } // Check the context extension (if any) if it can have references. if (context->has_extension() && !context->IsCatchContext()) { return JSObject::cast(context->extension())->ReferencesObject(obj); } } // No references to object. return false; } Handle JSObject::PreventExtensions(Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->PreventExtensions(), Object); } MaybeObject* JSObject::PreventExtensions() { Isolate* isolate = GetIsolate(); if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, isolate->heap()->undefined_value(), v8::ACCESS_KEYS)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_KEYS); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return this; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->PreventExtensions(); } // It's not possible to seal objects with external array elements if (HasExternalArrayElements()) { HandleScope scope(isolate); Handle object(this); Handle error = isolate->factory()->NewTypeError( "cant_prevent_ext_external_array_elements", HandleVector(&object, 1)); return isolate->Throw(*error); } // If there are fast elements we normalize. SeededNumberDictionary* dictionary = NULL; { MaybeObject* maybe = NormalizeElements(); if (!maybe->To(&dictionary)) return maybe; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Make sure that we never go back to fast case. dictionary->set_requires_slow_elements(); // Do a map transition, other objects with this map may still // be extensible. Map* new_map; { MaybeObject* maybe = map()->CopyDropTransitions(); if (!maybe->To(&new_map)) return maybe; } new_map->set_is_extensible(false); set_map(new_map); ASSERT(!map()->is_extensible()); return new_map; } // Tests for the fast common case for property enumeration: // - This object and all prototypes has an enum cache (which means that // it is no proxy, has no interceptors and needs no access checks). // - This object has no elements. // - No prototype has enumerable properties/elements. bool JSReceiver::IsSimpleEnum() { Heap* heap = GetHeap(); for (Object* o = this; o != heap->null_value(); o = JSObject::cast(o)->GetPrototype()) { if (!o->IsJSObject()) return false; JSObject* curr = JSObject::cast(o); if (!curr->map()->instance_descriptors()->HasEnumCache()) return false; ASSERT(!curr->HasNamedInterceptor()); ASSERT(!curr->HasIndexedInterceptor()); ASSERT(!curr->IsAccessCheckNeeded()); if (curr->NumberOfEnumElements() > 0) return false; if (curr != this) { FixedArray* curr_fixed_array = FixedArray::cast(curr->map()->instance_descriptors()->GetEnumCache()); if (curr_fixed_array->length() > 0) return false; } } return true; } int Map::NumberOfDescribedProperties(PropertyAttributes filter) { int result = 0; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); if (descs->IsProperty(i) && (details.attributes() & filter) == 0) { result++; } } return result; } int Map::PropertyIndexFor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && !descs->IsNullDescriptor(i)) { return descs->GetFieldIndex(i); } } return -1; } int Map::NextFreePropertyIndex() { int max_index = -1; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (descs->GetType(i) == FIELD) { int current_index = descs->GetFieldIndex(i); if (current_index > max_index) max_index = current_index; } } return max_index + 1; } AccessorDescriptor* Map::FindAccessor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && descs->GetType(i) == CALLBACKS) { return descs->GetCallbacks(i); } } return NULL; } void JSReceiver::LocalLookup(String* name, LookupResult* result) { ASSERT(name->IsString()); Heap* heap = GetHeap(); if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); return JSReceiver::cast(proto)->LocalLookup(name, result); } if (IsJSProxy()) { result->HandlerResult(JSProxy::cast(this)); return; } // Do not use inline caching if the object is a non-global object // that requires access checks. if (IsAccessCheckNeeded()) { result->DisallowCaching(); } JSObject* js_object = JSObject::cast(this); // Check __proto__ before interceptor. if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject()) { result->ConstantResult(js_object); return; } // Check for lookup interceptor except when bootstrapping. if (js_object->HasNamedInterceptor() && !heap->isolate()->bootstrapper()->IsActive()) { result->InterceptorResult(js_object); return; } js_object->LocalLookupRealNamedProperty(name, result); } void JSReceiver::Lookup(String* name, LookupResult* result) { // Ecma-262 3rd 8.6.2.4 Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value(); current = JSObject::cast(current)->GetPrototype()) { JSReceiver::cast(current)->LocalLookup(name, result); if (result->IsProperty()) return; } result->NotFound(); } // Search object and it's prototype chain for callback properties. void JSObject::LookupCallback(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value() && current->IsJSObject(); current = JSObject::cast(current)->GetPrototype()) { JSObject::cast(current)->LocalLookupRealNamedProperty(name, result); if (result->IsFound() && result->type() == CALLBACKS) return; } result->NotFound(); } // Try to update an accessor in an elements dictionary. Return true if the // update succeeded, and false otherwise. static bool UpdateGetterSetterInDictionary( SeededNumberDictionary* dictionary, uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { Object* result = dictionary->ValueAt(entry); PropertyDetails details = dictionary->DetailsAt(entry); if (details.type() == CALLBACKS && result->IsAccessorPair()) { ASSERT(!details.IsDontDelete()); if (details.attributes() != attributes) { dictionary->DetailsAtPut(entry, PropertyDetails(attributes, CALLBACKS, index)); } AccessorPair::cast(result)->SetComponents(getter, setter); return true; } } return false; } MaybeObject* JSObject::DefineElementAccessor(uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { switch (GetElementsKind()) { case FAST_SMI_ONLY_ELEMENTS: case FAST_ELEMENTS: case FAST_DOUBLE_ELEMENTS: break; case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: // Ignore getters and setters on pixel and external array elements. return GetHeap()->undefined_value(); case DICTIONARY_ELEMENTS: if (UpdateGetterSetterInDictionary(element_dictionary(), index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } break; case NON_STRICT_ARGUMENTS_ELEMENTS: { // Ascertain whether we have read-only properties or an existing // getter/setter pair in an arguments elements dictionary backing // store. FixedArray* parameter_map = FixedArray::cast(elements()); uint32_t length = parameter_map->length(); Object* probe = index < (length - 2) ? parameter_map->get(index + 2) : NULL; if (probe == NULL || probe->IsTheHole()) { FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); if (arguments->IsDictionary()) { SeededNumberDictionary* dictionary = SeededNumberDictionary::cast(arguments); if (UpdateGetterSetterInDictionary(dictionary, index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } } } break; } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetElementCallback(index, accessors, attributes); } MaybeObject* JSObject::DefinePropertyAccessor(String* name, Object* getter, Object* setter, PropertyAttributes attributes) { // Lookup the name. LookupResult result(GetHeap()->isolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsFound()) { if (result.type() == CALLBACKS) { ASSERT(!result.IsDontDelete()); Object* obj = result.GetCallbackObject(); // Need to preserve old getters/setters. if (obj->IsAccessorPair()) { AccessorPair* copy; { MaybeObject* maybe_copy = AccessorPair::cast(obj)->CopyWithoutTransitions(); if (!maybe_copy->To(©)) return maybe_copy; } copy->SetComponents(getter, setter); // Use set to update attributes. return SetPropertyCallback(name, copy, attributes); } } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetPropertyCallback(name, accessors, attributes); } bool JSObject::CanSetCallback(String* name) { ASSERT(!IsAccessCheckNeeded() || GetIsolate()->MayNamedAccess(this, name, v8::ACCESS_SET)); // Check if there is an API defined callback object which prohibits // callback overwriting in this object or it's prototype chain. // This mechanism is needed for instance in a browser setting, where // certain accessors such as window.location should not be allowed // to be overwritten because allowing overwriting could potentially // cause security problems. LookupResult callback_result(GetIsolate()); LookupCallback(name, &callback_result); if (callback_result.IsProperty()) { Object* obj = callback_result.GetCallbackObject(); if (obj->IsAccessorInfo() && AccessorInfo::cast(obj)->prohibits_overwriting()) { return false; } } return true; } MaybeObject* JSObject::SetElementCallback(uint32_t index, Object* structure, PropertyAttributes attributes) { PropertyDetails details = PropertyDetails(attributes, CALLBACKS); // Normalize elements to make this operation simple. SeededNumberDictionary* dictionary; { MaybeObject* maybe_dictionary = NormalizeElements(); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Update the dictionary with the new CALLBACKS property. { MaybeObject* maybe_dictionary = dictionary->Set(index, structure, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } dictionary->set_requires_slow_elements(); // Update the dictionary backing store on the object. if (elements()->map() == GetHeap()->non_strict_arguments_elements_map()) { // Also delete any parameter alias. // // TODO(kmillikin): when deleting the last parameter alias we could // switch to a direct backing store without the parameter map. This // would allow GC of the context. FixedArray* parameter_map = FixedArray::cast(elements()); if (index < static_cast
(this)->CodeIterateBody(v); break; case JS_GLOBAL_PROPERTY_CELL_TYPE: JSGlobalPropertyCell::BodyDescriptor::IterateBody(this, v); break; case HEAP_NUMBER_TYPE: case FILLER_TYPE: case BYTE_ARRAY_TYPE: case FREE_SPACE_TYPE: case EXTERNAL_PIXEL_ARRAY_TYPE: case EXTERNAL_BYTE_ARRAY_TYPE: case EXTERNAL_UNSIGNED_BYTE_ARRAY_TYPE: case EXTERNAL_SHORT_ARRAY_TYPE: case EXTERNAL_UNSIGNED_SHORT_ARRAY_TYPE: case EXTERNAL_INT_ARRAY_TYPE: case EXTERNAL_UNSIGNED_INT_ARRAY_TYPE: case EXTERNAL_FLOAT_ARRAY_TYPE: case EXTERNAL_DOUBLE_ARRAY_TYPE: break; case SHARED_FUNCTION_INFO_TYPE: { SharedFunctionInfo* shared = reinterpret_cast(this); shared->SharedFunctionInfoIterateBody(v); break; } #define MAKE_STRUCT_CASE(NAME, Name, name) \ case NAME##_TYPE: STRUCT_LIST(MAKE_STRUCT_CASE) #undef MAKE_STRUCT_CASE StructBodyDescriptor::IterateBody(this, object_size, v); break; default: PrintF("Unknown type: %d\n", type); UNREACHABLE(); } } Object* HeapNumber::HeapNumberToBoolean() { // NaN, +0, and -0 should return the false object #if __BYTE_ORDER == __LITTLE_ENDIAN union IeeeDoubleLittleEndianArchType u; #elif __BYTE_ORDER == __BIG_ENDIAN union IeeeDoubleBigEndianArchType u; #endif u.d = value(); if (u.bits.exp == 2047) { // Detect NaN for IEEE double precision floating point. if ((u.bits.man_low | u.bits.man_high) != 0) return GetHeap()->false_value(); } if (u.bits.exp == 0) { // Detect +0, and -0 for IEEE double precision floating point. if ((u.bits.man_low | u.bits.man_high) == 0) return GetHeap()->false_value(); } return GetHeap()->true_value(); } void HeapNumber::HeapNumberPrint(FILE* out) { PrintF(out, "%.16g", Number()); } void HeapNumber::HeapNumberPrint(StringStream* accumulator) { // The Windows version of vsnprintf can allocate when printing a %g string // into a buffer that may not be big enough. We don't want random memory // allocation when producing post-crash stack traces, so we print into a // buffer that is plenty big enough for any floating point number, then // print that using vsnprintf (which may truncate but never allocate if // there is no more space in the buffer). EmbeddedVector buffer; OS::SNPrintF(buffer, "%.16g", Number()); accumulator->Add("%s", buffer.start()); } String* JSReceiver::class_name() { if (IsJSFunction() && IsJSFunctionProxy()) { return GetHeap()->function_class_symbol(); } if (map()->constructor()->IsJSFunction()) { JSFunction* constructor = JSFunction::cast(map()->constructor()); return String::cast(constructor->shared()->instance_class_name()); } // If the constructor is not present, return "Object". return GetHeap()->Object_symbol(); } String* JSReceiver::constructor_name() { if (map()->constructor()->IsJSFunction()) { JSFunction* constructor = JSFunction::cast(map()->constructor()); String* name = String::cast(constructor->shared()->name()); if (name->length() > 0) return name; String* inferred_name = constructor->shared()->inferred_name(); if (inferred_name->length() > 0) return inferred_name; Object* proto = GetPrototype(); if (proto->IsJSObject()) return JSObject::cast(proto)->constructor_name(); } // TODO(rossberg): what about proxies? // If the constructor is not present, return "Object". return GetHeap()->Object_symbol(); } MaybeObject* JSObject::AddFastPropertyUsingMap(Map* new_map, String* name, Object* value) { int index = new_map->PropertyIndexFor(name); if (map()->unused_property_fields() == 0) { ASSERT(map()->unused_property_fields() == 0); int new_unused = new_map->unused_property_fields(); Object* values; { MaybeObject* maybe_values = properties()->CopySize(properties()->length() + new_unused + 1); if (!maybe_values->ToObject(&values)) return maybe_values; } set_properties(FixedArray::cast(values)); } set_map(new_map); return FastPropertyAtPut(index, value); } static bool IsIdentifier(UnicodeCache* cache, unibrow::CharacterStream* buffer) { // Checks whether the buffer contains an identifier (no escape). if (!buffer->has_more()) return false; if (!cache->IsIdentifierStart(buffer->GetNext())) { return false; } while (buffer->has_more()) { if (!cache->IsIdentifierPart(buffer->GetNext())) { return false; } } return true; } MaybeObject* JSObject::AddFastProperty(String* name, Object* value, PropertyAttributes attributes) { ASSERT(!IsJSGlobalProxy()); // Normalize the object if the name is an actual string (not the // hidden symbols) and is not a real identifier. Isolate* isolate = GetHeap()->isolate(); StringInputBuffer buffer(name); if (!IsIdentifier(isolate->unicode_cache(), &buffer) && name != isolate->heap()->hidden_symbol()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return AddSlowProperty(name, value, attributes); } DescriptorArray* old_descriptors = map()->instance_descriptors(); // Compute the new index for new field. int index = map()->NextFreePropertyIndex(); // Allocate new instance descriptors with (name, index) added FieldDescriptor new_field(name, index, attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = old_descriptors->CopyInsert(&new_field, REMOVE_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } } // Only allow map transition if the object isn't the global object and there // is not a transition for the name, or there's a transition for the name but // it's unrelated to properties. int descriptor_index = old_descriptors->Search(name); // Element transitions are stored in the descriptor for property "", which is // not a identifier and should have forced a switch to slow properties above. ASSERT(descriptor_index == DescriptorArray::kNotFound || old_descriptors->GetType(descriptor_index) != ELEMENTS_TRANSITION); bool can_insert_transition = descriptor_index == DescriptorArray::kNotFound || old_descriptors->GetType(descriptor_index) == ELEMENTS_TRANSITION; bool allow_map_transition = can_insert_transition && (isolate->context()->global_context()->object_function()->map() != map()); ASSERT(index < map()->inobject_properties() || (index - map()->inobject_properties()) < properties()->length() || map()->unused_property_fields() == 0); // Allocate a new map for the object. Object* r; { MaybeObject* maybe_r = map()->CopyDropDescriptors(); if (!maybe_r->ToObject(&r)) return maybe_r; } Map* new_map = Map::cast(r); if (allow_map_transition) { // Allocate new instance descriptors for the old map with map transition. MapTransitionDescriptor d(name, Map::cast(new_map), attributes); Object* r; { MaybeObject* maybe_r = old_descriptors->CopyInsert(&d, KEEP_TRANSITIONS); if (!maybe_r->ToObject(&r)) return maybe_r; } old_descriptors = DescriptorArray::cast(r); } if (map()->unused_property_fields() == 0) { if (properties()->length() > MaxFastProperties()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return AddSlowProperty(name, value, attributes); } // Make room for the new value Object* values; { MaybeObject* maybe_values = properties()->CopySize(properties()->length() + kFieldsAdded); if (!maybe_values->ToObject(&values)) return maybe_values; } set_properties(FixedArray::cast(values)); new_map->set_unused_property_fields(kFieldsAdded - 1); } else { new_map->set_unused_property_fields(map()->unused_property_fields() - 1); } // We have now allocated all the necessary objects. // All the changes can be applied at once, so they are atomic. map()->set_instance_descriptors(old_descriptors); new_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); set_map(new_map); return FastPropertyAtPut(index, value); } MaybeObject* JSObject::AddConstantFunctionProperty( String* name, JSFunction* function, PropertyAttributes attributes) { // Allocate new instance descriptors with (name, function) added ConstantFunctionDescriptor d(name, function, attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = map()->instance_descriptors()->CopyInsert(&d, REMOVE_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } } // Allocate a new map for the object. Object* new_map; { MaybeObject* maybe_new_map = map()->CopyDropDescriptors(); if (!maybe_new_map->ToObject(&new_map)) return maybe_new_map; } DescriptorArray* descriptors = DescriptorArray::cast(new_descriptors); Map::cast(new_map)->set_instance_descriptors(descriptors); Map* old_map = map(); set_map(Map::cast(new_map)); // If the old map is the global object map (from new Object()), // then transitions are not added to it, so we are done. Heap* heap = GetHeap(); if (old_map == heap->isolate()->context()->global_context()-> object_function()->map()) { return function; } // Do not add CONSTANT_TRANSITIONS to global objects if (IsGlobalObject()) { return function; } // Add a CONSTANT_TRANSITION descriptor to the old map, // so future assignments to this property on other objects // of the same type will create a normal field, not a constant function. // Don't do this for special properties, with non-trival attributes. if (attributes != NONE) { return function; } ConstTransitionDescriptor mark(name, Map::cast(new_map)); { MaybeObject* maybe_new_descriptors = old_map->instance_descriptors()->CopyInsert(&mark, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { // We have accomplished the main goal, so return success. return function; } } old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return function; } // Add property in slow mode MaybeObject* JSObject::AddSlowProperty(String* name, Object* value, PropertyAttributes attributes) { ASSERT(!HasFastProperties()); StringDictionary* dict = property_dictionary(); Object* store_value = value; if (IsGlobalObject()) { // In case name is an orphaned property reuse the cell. int entry = dict->FindEntry(name); if (entry != StringDictionary::kNotFound) { store_value = dict->ValueAt(entry); JSGlobalPropertyCell::cast(store_value)->set_value(value); // Assign an enumeration index to the property and update // SetNextEnumerationIndex. int index = dict->NextEnumerationIndex(); PropertyDetails details = PropertyDetails(attributes, NORMAL, index); dict->SetNextEnumerationIndex(index + 1); dict->SetEntry(entry, name, store_value, details); return value; } Heap* heap = GetHeap(); { MaybeObject* maybe_store_value = heap->AllocateJSGlobalPropertyCell(value); if (!maybe_store_value->ToObject(&store_value)) return maybe_store_value; } JSGlobalPropertyCell::cast(store_value)->set_value(value); } PropertyDetails details = PropertyDetails(attributes, NORMAL); Object* result; { MaybeObject* maybe_result = dict->Add(name, store_value, details); if (!maybe_result->ToObject(&result)) return maybe_result; } if (dict != result) set_properties(StringDictionary::cast(result)); return value; } MaybeObject* JSObject::AddProperty(String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); Heap* heap = GetHeap(); if (!map_of_this->is_extensible()) { if (strict_mode == kNonStrictMode) { return value; } else { Handle args[1] = {Handle(name)}; return heap->isolate()->Throw( *FACTORY->NewTypeError("object_not_extensible", HandleVector(args, 1))); } } if (HasFastProperties()) { // Ensure the descriptor array does not get too big. if (map_of_this->instance_descriptors()->number_of_descriptors() < DescriptorArray::kMaxNumberOfDescriptors) { if (value->IsJSFunction()) { return AddConstantFunctionProperty(name, JSFunction::cast(value), attributes); } else { return AddFastProperty(name, value, attributes); } } else { // Normalize the object to prevent very large instance descriptors. // This eliminates unwanted N^2 allocation and lookup behavior. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } } } return AddSlowProperty(name, value, attributes); } MaybeObject* JSObject::SetPropertyPostInterceptor( String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsFound()) { // An existing property, a map transition or a null descriptor was // found. Use set property to handle all these cases. return SetProperty(&result, name, value, attributes, strict_mode); } bool found = false; MaybeObject* result_object; result_object = SetPropertyWithCallbackSetterInPrototypes(name, value, attributes, &found, strict_mode); if (found) return result_object; // Add a new real property. return AddProperty(name, value, attributes, strict_mode); } MaybeObject* JSObject::ReplaceSlowProperty(String* name, Object* value, PropertyAttributes attributes) { StringDictionary* dictionary = property_dictionary(); int old_index = dictionary->FindEntry(name); int new_enumeration_index = 0; // 0 means "Use the next available index." if (old_index != -1) { // All calls to ReplaceSlowProperty have had all transitions removed. ASSERT(!dictionary->ContainsTransition(old_index)); new_enumeration_index = dictionary->DetailsAt(old_index).index(); } PropertyDetails new_details(attributes, NORMAL, new_enumeration_index); return SetNormalizedProperty(name, value, new_details); } MaybeObject* JSObject::ConvertDescriptorToFieldAndMapTransition( String* name, Object* new_value, PropertyAttributes attributes) { Map* old_map = map(); Object* result; { MaybeObject* maybe_result = ConvertDescriptorToField(name, new_value, attributes); if (!maybe_result->ToObject(&result)) return maybe_result; } // If we get to this point we have succeeded - do not return failure // after this point. Later stuff is optional. if (!HasFastProperties()) { return result; } // Do not add transitions to the map of "new Object()". if (map() == GetIsolate()->context()->global_context()-> object_function()->map()) { return result; } MapTransitionDescriptor transition(name, map(), attributes); Object* new_descriptors; { MaybeObject* maybe_new_descriptors = old_map->instance_descriptors()-> CopyInsert(&transition, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return result; // Yes, return _result_. } } old_map->set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return result; } MaybeObject* JSObject::ConvertDescriptorToField(String* name, Object* new_value, PropertyAttributes attributes) { if (map()->unused_property_fields() == 0 && properties()->length() > MaxFastProperties()) { Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return ReplaceSlowProperty(name, new_value, attributes); } int index = map()->NextFreePropertyIndex(); FieldDescriptor new_field(name, index, attributes); // Make a new DescriptorArray replacing an entry with FieldDescriptor. Object* descriptors_unchecked; { MaybeObject* maybe_descriptors_unchecked = map()->instance_descriptors()-> CopyInsert(&new_field, REMOVE_TRANSITIONS); if (!maybe_descriptors_unchecked->ToObject(&descriptors_unchecked)) { return maybe_descriptors_unchecked; } } DescriptorArray* new_descriptors = DescriptorArray::cast(descriptors_unchecked); // Make a new map for the object. Object* new_map_unchecked; { MaybeObject* maybe_new_map_unchecked = map()->CopyDropDescriptors(); if (!maybe_new_map_unchecked->ToObject(&new_map_unchecked)) { return maybe_new_map_unchecked; } } Map* new_map = Map::cast(new_map_unchecked); new_map->set_instance_descriptors(new_descriptors); // Make new properties array if necessary. FixedArray* new_properties = 0; // Will always be NULL or a valid pointer. int new_unused_property_fields = map()->unused_property_fields() - 1; if (map()->unused_property_fields() == 0) { new_unused_property_fields = kFieldsAdded - 1; Object* new_properties_object; { MaybeObject* maybe_new_properties_object = properties()->CopySize(properties()->length() + kFieldsAdded); if (!maybe_new_properties_object->ToObject(&new_properties_object)) { return maybe_new_properties_object; } } new_properties = FixedArray::cast(new_properties_object); } // Update pointers to commit changes. // Object points to the new map. new_map->set_unused_property_fields(new_unused_property_fields); set_map(new_map); if (new_properties) { set_properties(FixedArray::cast(new_properties)); } return FastPropertyAtPut(index, new_value); } MaybeObject* JSObject::SetPropertyWithInterceptor( String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle this_handle(this); Handle name_handle(name); Handle value_handle(value, isolate); Handle interceptor(GetNamedInterceptor()); if (!interceptor->setter()->IsUndefined()) { LOG(isolate, ApiNamedPropertyAccess("interceptor-named-set", this, name)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::NamedPropertySetter setter = v8::ToCData(interceptor->setter()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); Handle value_unhole(value->IsTheHole() ? isolate->heap()->undefined_value() : value, isolate); result = setter(v8::Utils::ToLocal(name_handle), v8::Utils::ToLocal(value_unhole), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) return *value_handle; } MaybeObject* raw_result = this_handle->SetPropertyPostInterceptor(*name_handle, *value_handle, attributes, strict_mode); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } Handle JSReceiver::SetProperty(Handle object, Handle key, Handle value, PropertyAttributes attributes, StrictModeFlag strict_mode) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->SetProperty(*key, *value, attributes, strict_mode), Object); } MaybeObject* JSReceiver::SetProperty(String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { LookupResult result(GetIsolate()); LocalLookup(name, &result); return SetProperty(&result, name, value, attributes, strict_mode); } MaybeObject* JSObject::SetPropertyWithCallback(Object* structure, String* name, Object* value, JSObject* holder, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); // We should never get here to initialize a const with the hole // value since a const declaration would conflict with the setter. ASSERT(!value->IsTheHole()); Handle value_handle(value, isolate); // To accommodate both the old and the new api we switch on the // data structure used to store the callbacks. Eventually foreign // callbacks should be phased out. if (structure->IsForeign()) { AccessorDescriptor* callback = reinterpret_cast( Foreign::cast(structure)->foreign_address()); MaybeObject* obj = (callback->setter)(this, value, callback->data); RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (obj->IsFailure()) return obj; return *value_handle; } if (structure->IsAccessorInfo()) { // api style callbacks AccessorInfo* data = AccessorInfo::cast(structure); Object* call_obj = data->setter(); v8::AccessorSetter call_fun = v8::ToCData(call_obj); if (call_fun == NULL) return value; Handle key(name); LOG(isolate, ApiNamedPropertyAccess("store", this, name)); CustomArguments args(isolate, data->data(), this, JSObject::cast(holder)); v8::AccessorInfo info(args.end()); { // Leaving JavaScript. VMState state(isolate, EXTERNAL); call_fun(v8::Utils::ToLocal(key), v8::Utils::ToLocal(value_handle), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); return *value_handle; } if (structure->IsAccessorPair()) { Object* setter = AccessorPair::cast(structure)->setter(); if (setter->IsSpecFunction()) { // TODO(rossberg): nicer would be to cast to some JSCallable here... return SetPropertyWithDefinedSetter(JSReceiver::cast(setter), value); } else { if (strict_mode == kNonStrictMode) { return value; } Handle key(name); Handle holder_handle(holder, isolate); Handle args[2] = { key, holder_handle }; return isolate->Throw( *isolate->factory()->NewTypeError("no_setter_in_callback", HandleVector(args, 2))); } } UNREACHABLE(); return NULL; } MaybeObject* JSReceiver::SetPropertyWithDefinedSetter(JSReceiver* setter, Object* value) { Isolate* isolate = GetIsolate(); Handle value_handle(value, isolate); Handle fun(setter, isolate); Handle self(this, isolate); #ifdef ENABLE_DEBUGGER_SUPPORT Debug* debug = isolate->debug(); // Handle stepping into a setter if step into is active. // TODO(rossberg): should this apply to getters that are function proxies? if (debug->StepInActive() && fun->IsJSFunction()) { debug->HandleStepIn( Handle::cast(fun), Handle::null(), 0, false); } #endif bool has_pending_exception; Handle argv[] = { value_handle }; Execution::Call(fun, self, ARRAY_SIZE(argv), argv, &has_pending_exception); // Check for pending exception and return the result. if (has_pending_exception) return Failure::Exception(); return *value_handle; } void JSObject::LookupCallbackSetterInPrototypes(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = pt->GetPrototype()) { if (pt->IsJSProxy()) { return result->HandlerResult(JSProxy::cast(pt)); } JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); if (result->IsProperty()) { if (result->type() == CALLBACKS && !result->IsReadOnly()) return; // Found non-callback or read-only callback, stop looking. break; } } result->NotFound(); } MaybeObject* JSObject::SetElementWithCallbackSetterInPrototypes( uint32_t index, Object* value, bool* found, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = pt->GetPrototype()) { if (pt->IsJSProxy()) { String* name; MaybeObject* maybe = GetHeap()->Uint32ToString(index); if (!maybe->To(&name)) { *found = true; // Force abort return maybe; } return JSProxy::cast(pt)->SetPropertyWithHandlerIfDefiningSetter( name, value, NONE, strict_mode, found); } if (!JSObject::cast(pt)->HasDictionaryElements()) { continue; } SeededNumberDictionary* dictionary = JSObject::cast(pt)->element_dictionary(); int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { PropertyDetails details = dictionary->DetailsAt(entry); if (details.type() == CALLBACKS) { *found = true; return SetElementWithCallback(dictionary->ValueAt(entry), index, value, JSObject::cast(pt), strict_mode); } } } *found = false; return heap->the_hole_value(); } MaybeObject* JSObject::SetPropertyWithCallbackSetterInPrototypes( String* name, Object* value, PropertyAttributes attributes, bool* found, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); // We could not find a local property so let's check whether there is an // accessor that wants to handle the property. LookupResult accessor_result(heap->isolate()); LookupCallbackSetterInPrototypes(name, &accessor_result); if (accessor_result.IsFound()) { *found = true; if (accessor_result.type() == CALLBACKS) { return SetPropertyWithCallback(accessor_result.GetCallbackObject(), name, value, accessor_result.holder(), strict_mode); } else if (accessor_result.type() == HANDLER) { // There is a proxy in the prototype chain. Invoke its // getPropertyDescriptor trap. bool found = false; // SetPropertyWithHandlerIfDefiningSetter can cause GC, // make sure to use the handlified references after calling // the function. Handle self(this); Handle hname(name); Handle hvalue(value); MaybeObject* result = accessor_result.proxy()->SetPropertyWithHandlerIfDefiningSetter( name, value, attributes, strict_mode, &found); if (found) return result; // The proxy does not define the property as an accessor. // Consequently, it has no effect on setting the receiver. return self->AddProperty(*hname, *hvalue, attributes, strict_mode); } } *found = false; return heap->the_hole_value(); } void JSObject::LookupInDescriptor(String* name, LookupResult* result) { DescriptorArray* descriptors = map()->instance_descriptors(); int number = descriptors->SearchWithCache(name); if (number != DescriptorArray::kNotFound) { result->DescriptorResult(this, descriptors->GetDetails(number), number); } else { result->NotFound(); } } void Map::LookupInDescriptors(JSObject* holder, String* name, LookupResult* result) { DescriptorArray* descriptors = instance_descriptors(); DescriptorLookupCache* cache = GetHeap()->isolate()->descriptor_lookup_cache(); int number = cache->Lookup(descriptors, name); if (number == DescriptorLookupCache::kAbsent) { number = descriptors->Search(name); cache->Update(descriptors, name, number); } if (number != DescriptorArray::kNotFound) { result->DescriptorResult(holder, descriptors->GetDetails(number), number); } else { result->NotFound(); } } static bool ContainsMap(MapHandleList* maps, Handle map) { ASSERT(!map.is_null()); for (int i = 0; i < maps->length(); ++i) { if (!maps->at(i).is_null() && maps->at(i).is_identical_to(map)) return true; } return false; } template static Handle MaybeNull(T* p) { if (p == NULL) return Handle::null(); return Handle(p); } Handle Map::FindTransitionedMap(MapHandleList* candidates) { ElementsKind elms_kind = elements_kind(); if (elms_kind == FAST_DOUBLE_ELEMENTS) { bool dummy = true; Handle fast_map = MaybeNull(LookupElementsTransitionMap(FAST_ELEMENTS, &dummy)); if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { return fast_map; } return Handle::null(); } if (elms_kind == FAST_SMI_ONLY_ELEMENTS) { bool dummy = true; Handle double_map = MaybeNull(LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, &dummy)); // In the current implementation, if the DOUBLE map doesn't exist, the // FAST map can't exist either. if (double_map.is_null()) return Handle::null(); Handle fast_map = MaybeNull(double_map->LookupElementsTransitionMap(FAST_ELEMENTS, &dummy)); if (!fast_map.is_null() && ContainsMap(candidates, fast_map)) { return fast_map; } if (ContainsMap(candidates, double_map)) return double_map; } return Handle::null(); } static Map* GetElementsTransitionMapFromDescriptor(Object* descriptor_contents, ElementsKind elements_kind) { if (descriptor_contents->IsMap()) { Map* map = Map::cast(descriptor_contents); if (map->elements_kind() == elements_kind) { return map; } return NULL; } FixedArray* map_array = FixedArray::cast(descriptor_contents); for (int i = 0; i < map_array->length(); ++i) { Object* current = map_array->get(i); // Skip undefined slots, they are sentinels for reclaimed maps. if (!current->IsUndefined()) { Map* current_map = Map::cast(map_array->get(i)); if (current_map->elements_kind() == elements_kind) { return current_map; } } } return NULL; } static MaybeObject* AddElementsTransitionMapToDescriptor( Object* descriptor_contents, Map* new_map) { // Nothing was in the descriptor for an ELEMENTS_TRANSITION, // simply add the map. if (descriptor_contents == NULL) { return new_map; } // There was already a map in the descriptor, create a 2-element FixedArray // to contain the existing map plus the new one. FixedArray* new_array; Heap* heap = new_map->GetHeap(); if (descriptor_contents->IsMap()) { // Must tenure, DescriptorArray expects no new-space objects. MaybeObject* maybe_new_array = heap->AllocateFixedArray(2, TENURED); if (!maybe_new_array->To(&new_array)) { return maybe_new_array; } new_array->set(0, descriptor_contents); new_array->set(1, new_map); return new_array; } // The descriptor already contained a list of maps for different ElementKinds // of ELEMENTS_TRANSITION, first check the existing array for an undefined // slot, and if that's not available, create a FixedArray to hold the existing // maps plus the new one and fill it in. FixedArray* array = FixedArray::cast(descriptor_contents); for (int i = 0; i < array->length(); ++i) { if (array->get(i)->IsUndefined()) { array->set(i, new_map); return array; } } // Must tenure, DescriptorArray expects no new-space objects. MaybeObject* maybe_new_array = heap->AllocateFixedArray(array->length() + 1, TENURED); if (!maybe_new_array->To(&new_array)) { return maybe_new_array; } int i = 0; while (i < array->length()) { new_array->set(i, array->get(i)); ++i; } new_array->set(i, new_map); return new_array; } String* Map::elements_transition_sentinel_name() { return GetHeap()->empty_symbol(); } Object* Map::GetDescriptorContents(String* sentinel_name, bool* safe_to_add_transition) { // Get the cached index for the descriptors lookup, or find and cache it. DescriptorArray* descriptors = instance_descriptors(); DescriptorLookupCache* cache = GetIsolate()->descriptor_lookup_cache(); int index = cache->Lookup(descriptors, sentinel_name); if (index == DescriptorLookupCache::kAbsent) { index = descriptors->Search(sentinel_name); cache->Update(descriptors, sentinel_name, index); } // If the transition already exists, return its descriptor. if (index != DescriptorArray::kNotFound) { PropertyDetails details(descriptors->GetDetails(index)); if (details.type() == ELEMENTS_TRANSITION) { return descriptors->GetValue(index); } else { if (safe_to_add_transition != NULL) { *safe_to_add_transition = false; } } } return NULL; } Map* Map::LookupElementsTransitionMap(ElementsKind elements_kind, bool* safe_to_add_transition) { // Special case: indirect SMI->FAST transition (cf. comment in // AddElementsTransition()). if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && elements_kind == FAST_ELEMENTS) { Map* double_map = this->LookupElementsTransitionMap(FAST_DOUBLE_ELEMENTS, safe_to_add_transition); if (double_map == NULL) return double_map; return double_map->LookupElementsTransitionMap(FAST_ELEMENTS, safe_to_add_transition); } Object* descriptor_contents = GetDescriptorContents( elements_transition_sentinel_name(), safe_to_add_transition); if (descriptor_contents != NULL) { Map* maybe_transition_map = GetElementsTransitionMapFromDescriptor(descriptor_contents, elements_kind); ASSERT(maybe_transition_map == NULL || maybe_transition_map->IsMap()); return maybe_transition_map; } return NULL; } MaybeObject* Map::AddElementsTransition(ElementsKind elements_kind, Map* transitioned_map) { // The map transition graph should be a tree, therefore the transition // from SMI to FAST elements is not done directly, but by going through // DOUBLE elements first. if (this->elements_kind() == FAST_SMI_ONLY_ELEMENTS && elements_kind == FAST_ELEMENTS) { bool safe_to_add = true; Map* double_map = this->LookupElementsTransitionMap( FAST_DOUBLE_ELEMENTS, &safe_to_add); // This method is only called when safe_to_add_transition has been found // to be true earlier. ASSERT(safe_to_add); if (double_map == NULL) { MaybeObject* maybe_map = this->CopyDropTransitions(); if (!maybe_map->To(&double_map)) return maybe_map; double_map->set_elements_kind(FAST_DOUBLE_ELEMENTS); MaybeObject* maybe_double_transition = this->AddElementsTransition( FAST_DOUBLE_ELEMENTS, double_map); if (maybe_double_transition->IsFailure()) return maybe_double_transition; } return double_map->AddElementsTransition(FAST_ELEMENTS, transitioned_map); } bool safe_to_add_transition = true; Object* descriptor_contents = GetDescriptorContents( elements_transition_sentinel_name(), &safe_to_add_transition); // This method is only called when safe_to_add_transition has been found // to be true earlier. ASSERT(safe_to_add_transition); MaybeObject* maybe_new_contents = AddElementsTransitionMapToDescriptor(descriptor_contents, transitioned_map); Object* new_contents; if (!maybe_new_contents->ToObject(&new_contents)) { return maybe_new_contents; } ElementsTransitionDescriptor desc(elements_transition_sentinel_name(), new_contents); Object* new_descriptors; MaybeObject* maybe_new_descriptors = instance_descriptors()->CopyInsert(&desc, KEEP_TRANSITIONS); if (!maybe_new_descriptors->ToObject(&new_descriptors)) { return maybe_new_descriptors; } set_instance_descriptors(DescriptorArray::cast(new_descriptors)); return this; } Handle JSObject::GetElementsTransitionMap(Handle object, ElementsKind to_kind) { Isolate* isolate = object->GetIsolate(); CALL_HEAP_FUNCTION(isolate, object->GetElementsTransitionMap(isolate, to_kind), Map); } MaybeObject* JSObject::GetElementsTransitionMapSlow(ElementsKind to_kind) { Map* current_map = map(); ElementsKind from_kind = current_map->elements_kind(); if (from_kind == to_kind) return current_map; // Only objects with FastProperties can have DescriptorArrays and can track // element-related maps. Also don't add descriptors to maps that are shared. bool safe_to_add_transition = HasFastProperties() && !current_map->IsUndefined() && !current_map->is_shared(); // Prevent long chains of DICTIONARY -> FAST_ELEMENTS maps caused by objects // with elements that switch back and forth between dictionary and fast // element mode. if (from_kind == DICTIONARY_ELEMENTS && to_kind == FAST_ELEMENTS) { safe_to_add_transition = false; } if (safe_to_add_transition) { // It's only safe to manipulate the descriptor array if it would be // safe to add a transition. Map* maybe_transition_map = current_map->LookupElementsTransitionMap( to_kind, &safe_to_add_transition); if (maybe_transition_map != NULL) { return maybe_transition_map; } } Map* new_map = NULL; // No transition to an existing map for the given ElementsKind. Make a new // one. { MaybeObject* maybe_map = current_map->CopyDropTransitions(); if (!maybe_map->To(&new_map)) return maybe_map; } new_map->set_elements_kind(to_kind); // Only remember the map transition if the object's map is NOT equal to the // global object_function's map and there is not an already existing // non-matching element transition. Context* global_context = GetIsolate()->context()->global_context(); bool allow_map_transition = safe_to_add_transition && (global_context->object_function()->map() != map()); if (allow_map_transition) { MaybeObject* maybe_transition = current_map->AddElementsTransition(to_kind, new_map); if (maybe_transition->IsFailure()) return maybe_transition; } return new_map; } void JSObject::LocalLookupRealNamedProperty(String* name, LookupResult* result) { if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); // A GlobalProxy's prototype should always be a proper JSObject. return JSObject::cast(proto)->LocalLookupRealNamedProperty(name, result); } if (HasFastProperties()) { LookupInDescriptor(name, result); if (result->IsFound()) { // A property, a map transition or a null descriptor was found. // We return all of these result types because // LocalLookupRealNamedProperty is used when setting properties // where map transitions and null descriptors are handled. ASSERT(result->holder() == this && result->type() != NORMAL); // Disallow caching for uninitialized constants. These can only // occur as fields. if (result->IsReadOnly() && result->type() == FIELD && FastPropertyAt(result->GetFieldIndex())->IsTheHole()) { result->DisallowCaching(); } return; } } else { int entry = property_dictionary()->FindEntry(name); if (entry != StringDictionary::kNotFound) { Object* value = property_dictionary()->ValueAt(entry); if (IsGlobalObject()) { PropertyDetails d = property_dictionary()->DetailsAt(entry); if (d.IsDeleted()) { result->NotFound(); return; } value = JSGlobalPropertyCell::cast(value)->value(); } // Make sure to disallow caching for uninitialized constants // found in the dictionary-mode objects. if (value->IsTheHole()) result->DisallowCaching(); result->DictionaryResult(this, entry); return; } } result->NotFound(); } void JSObject::LookupRealNamedProperty(String* name, LookupResult* result) { LocalLookupRealNamedProperty(name, result); if (result->IsProperty()) return; LookupRealNamedPropertyInPrototypes(name, result); } void JSObject::LookupRealNamedPropertyInPrototypes(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* pt = GetPrototype(); pt != heap->null_value(); pt = JSObject::cast(pt)->GetPrototype()) { JSObject::cast(pt)->LocalLookupRealNamedProperty(name, result); if (result->IsProperty() && (result->type() != INTERCEPTOR)) return; } result->NotFound(); } // We only need to deal with CALLBACKS and INTERCEPTORS MaybeObject* JSObject::SetPropertyWithFailedAccessCheck( LookupResult* result, String* name, Object* value, bool check_prototype, StrictModeFlag strict_mode) { if (check_prototype && !result->IsProperty()) { LookupCallbackSetterInPrototypes(name, result); } if (result->IsProperty()) { if (!result->IsReadOnly()) { switch (result->type()) { case CALLBACKS: { Object* obj = result->GetCallbackObject(); if (obj->IsAccessorInfo()) { AccessorInfo* info = AccessorInfo::cast(obj); if (info->all_can_write()) { return SetPropertyWithCallback(result->GetCallbackObject(), name, value, result->holder(), strict_mode); } } break; } case INTERCEPTOR: { // Try lookup real named properties. Note that only property can be // set is callbacks marked as ALL_CAN_WRITE on the prototype chain. LookupResult r(GetIsolate()); LookupRealNamedProperty(name, &r); if (r.IsProperty()) { return SetPropertyWithFailedAccessCheck(&r, name, value, check_prototype, strict_mode); } break; } default: { break; } } } } Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle value_handle(value); isolate->ReportFailedAccessCheck(this, v8::ACCESS_SET); return *value_handle; } MaybeObject* JSReceiver::SetProperty(LookupResult* result, String* key, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { if (result->IsFound() && result->type() == HANDLER) { return result->proxy()->SetPropertyWithHandler( key, value, attributes, strict_mode); } else { return JSObject::cast(this)->SetPropertyForResult( result, key, value, attributes, strict_mode); } } bool JSProxy::HasPropertyWithHandler(String* name_raw) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "has", isolate->derived_has_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); return result->ToBoolean()->IsTrue(); } MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandler( String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle value(value_raw); Handle args[] = { receiver, name, value }; CallTrap("set", isolate->derived_set_trap(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); return *value; } MUST_USE_RESULT MaybeObject* JSProxy::SetPropertyWithHandlerIfDefiningSetter( String* name_raw, Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode, bool* found) { *found = true; // except where defined otherwise... Isolate* isolate = GetHeap()->isolate(); Handle proxy(this); Handle handler(this->handler()); // Trap might morph proxy. Handle name(name_raw); Handle value(value_raw); Handle args[] = { name }; Handle result = proxy->CallTrap( "getPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); if (!result->IsUndefined()) { // The proxy handler cares about this property. // Check whether it is virtualized as an accessor. // Emulate [[GetProperty]] semantics for proxies. bool has_pending_exception; Handle argv[] = { result }; Handle desc = Execution::Call(isolate->to_complete_property_descriptor(), result, ARRAY_SIZE(argv), argv, &has_pending_exception); if (has_pending_exception) return Failure::Exception(); Handle conf_name = isolate->factory()->LookupAsciiSymbol("configurable_"); Handle configurable(v8::internal::GetProperty(desc, conf_name)); ASSERT(!isolate->has_pending_exception()); if (configurable->IsFalse()) { Handle trap = isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); Handle args[] = { handler, trap, name }; Handle error = isolate->factory()->NewTypeError( "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); } ASSERT(configurable->IsTrue()); // Check for AccessorDescriptor. Handle set_name = isolate->factory()->LookupAsciiSymbol("set_"); Handle setter(v8::internal::GetProperty(desc, set_name)); ASSERT(!isolate->has_pending_exception()); if (!setter->IsUndefined()) { // We have a setter -- invoke it. // TODO(rossberg): nicer would be to cast to some JSCallable here... return proxy->SetPropertyWithDefinedSetter( JSReceiver::cast(*setter), *value); } else { Handle get_name = isolate->factory()->LookupAsciiSymbol("get_"); Handle getter(v8::internal::GetProperty(desc, get_name)); ASSERT(!isolate->has_pending_exception()); if (!getter->IsUndefined()) { // We have a getter but no setter -- the property may not be // written. In strict mode, throw an error. if (strict_mode == kNonStrictMode) return *value; Handle args[] = { name, proxy }; Handle error = isolate->factory()->NewTypeError( "no_setter_in_callback", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); } } // Fall-through. } // The proxy does not define the property as an accessor. *found = false; return *value; } MUST_USE_RESULT MaybeObject* JSProxy::DeletePropertyWithHandler( String* name_raw, DeleteMode mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle receiver(this); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "delete", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return Failure::Exception(); Object* bool_result = result->ToBoolean(); if (mode == STRICT_DELETION && bool_result == GetHeap()->false_value()) { Handle trap_name = isolate->factory()->LookupAsciiSymbol("delete"); Handle args[] = { Handle(handler()), trap_name }; Handle error = isolate->factory()->NewTypeError( "handler_failed", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return Failure::Exception(); } return bool_result; } MUST_USE_RESULT MaybeObject* JSProxy::DeleteElementWithHandler( uint32_t index, DeleteMode mode) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle name = isolate->factory()->Uint32ToString(index); return JSProxy::DeletePropertyWithHandler(*name, mode); } MUST_USE_RESULT PropertyAttributes JSProxy::GetPropertyAttributeWithHandler( JSReceiver* receiver_raw, String* name_raw) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle proxy(this); Handle handler(this->handler()); // Trap might morph proxy. Handle receiver(receiver_raw); Handle name(name_raw); Handle args[] = { name }; Handle result = CallTrap( "getPropertyDescriptor", Handle(), ARRAY_SIZE(args), args); if (isolate->has_pending_exception()) return NONE; if (result->IsUndefined()) return ABSENT; bool has_pending_exception; Handle argv[] = { result }; Handle desc = Execution::Call(isolate->to_complete_property_descriptor(), result, ARRAY_SIZE(argv), argv, &has_pending_exception); if (has_pending_exception) return NONE; // Convert result to PropertyAttributes. Handle enum_n = isolate->factory()->LookupAsciiSymbol("enumerable"); Handle enumerable(v8::internal::GetProperty(desc, enum_n)); if (isolate->has_pending_exception()) return NONE; Handle conf_n = isolate->factory()->LookupAsciiSymbol("configurable"); Handle configurable(v8::internal::GetProperty(desc, conf_n)); if (isolate->has_pending_exception()) return NONE; Handle writ_n = isolate->factory()->LookupAsciiSymbol("writable"); Handle writable(v8::internal::GetProperty(desc, writ_n)); if (isolate->has_pending_exception()) return NONE; if (configurable->IsFalse()) { Handle trap = isolate->factory()->LookupAsciiSymbol("getPropertyDescriptor"); Handle args[] = { handler, trap, name }; Handle error = isolate->factory()->NewTypeError( "proxy_prop_not_configurable", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return NONE; } int attributes = NONE; if (enumerable->ToBoolean()->IsFalse()) attributes |= DONT_ENUM; if (configurable->ToBoolean()->IsFalse()) attributes |= DONT_DELETE; if (writable->ToBoolean()->IsFalse()) attributes |= READ_ONLY; return static_cast(attributes); } MUST_USE_RESULT PropertyAttributes JSProxy::GetElementAttributeWithHandler( JSReceiver* receiver, uint32_t index) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle name = isolate->factory()->Uint32ToString(index); return GetPropertyAttributeWithHandler(receiver, *name); } void JSProxy::Fix() { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle self(this); // Save identity hash. MaybeObject* maybe_hash = GetIdentityHash(OMIT_CREATION); if (IsJSFunctionProxy()) { isolate->factory()->BecomeJSFunction(self); // Code will be set on the JavaScript side. } else { isolate->factory()->BecomeJSObject(self); } ASSERT(self->IsJSObject()); // Inherit identity, if it was present. Object* hash; if (maybe_hash->To(&hash) && hash->IsSmi()) { Handle new_self(JSObject::cast(*self)); isolate->factory()->SetIdentityHash(new_self, hash); } } MUST_USE_RESULT Handle JSProxy::CallTrap(const char* name, Handle derived, int argc, Handle argv[]) { Isolate* isolate = GetIsolate(); Handle handler(this->handler()); Handle trap_name = isolate->factory()->LookupAsciiSymbol(name); Handle trap(v8::internal::GetProperty(handler, trap_name)); if (isolate->has_pending_exception()) return trap; if (trap->IsUndefined()) { if (derived.is_null()) { Handle args[] = { handler, trap_name }; Handle error = isolate->factory()->NewTypeError( "handler_trap_missing", HandleVector(args, ARRAY_SIZE(args))); isolate->Throw(*error); return Handle(); } trap = Handle(derived); } bool threw; return Execution::Call(trap, handler, argc, argv, &threw); } MaybeObject* JSObject::SetPropertyForResult(LookupResult* result, String* name, Object* value, PropertyAttributes attributes, StrictModeFlag strict_mode) { Heap* heap = GetHeap(); // Make sure that the top context does not change when doing callbacks or // interceptor calls. AssertNoContextChange ncc; // Optimization for 2-byte strings often used as keys in a decompression // dictionary. We make these short keys into symbols to avoid constantly // reallocating them. if (!name->IsSymbol() && name->length() <= 2) { Object* symbol_version; { MaybeObject* maybe_symbol_version = heap->LookupSymbol(name); if (maybe_symbol_version->ToObject(&symbol_version)) { name = String::cast(symbol_version); } } } // Check access rights if needed. if (IsAccessCheckNeeded()) { if (!heap->isolate()->MayNamedAccess(this, name, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck( result, name, value, true, strict_mode); } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetPropertyForResult( result, name, value, attributes, strict_mode); } if (!result->IsProperty() && !IsJSContextExtensionObject()) { bool found = false; MaybeObject* result_object; result_object = SetPropertyWithCallbackSetterInPrototypes(name, value, attributes, &found, strict_mode); if (found) return result_object; } // At this point, no GC should have happened, as this would invalidate // 'result', which we cannot handlify! if (!result->IsFound()) { // Neither properties nor transitions found. return AddProperty(name, value, attributes, strict_mode); } if (result->IsReadOnly() && result->IsProperty()) { if (strict_mode == kStrictMode) { Handle self(this); Handle hname(name); Handle args[] = { hname, self }; return heap->isolate()->Throw(*heap->isolate()->factory()->NewTypeError( "strict_read_only_property", HandleVector(args, ARRAY_SIZE(args)))); } else { return value; } } // This is a real property that is not read-only, or it is a // transition or null descriptor and there are no setters in the prototypes. switch (result->type()) { case NORMAL: return SetNormalizedProperty(result, value); case FIELD: return FastPropertyAtPut(result->GetFieldIndex(), value); case MAP_TRANSITION: if (attributes == result->GetAttributes()) { // Only use map transition if the attributes match. return AddFastPropertyUsingMap(result->GetTransitionMap(), name, value); } return ConvertDescriptorToField(name, value, attributes); case CONSTANT_FUNCTION: // Only replace the function if necessary. if (value == result->GetConstantFunction()) return value; // Preserve the attributes of this existing property. attributes = result->GetAttributes(); return ConvertDescriptorToField(name, value, attributes); case CALLBACKS: return SetPropertyWithCallback(result->GetCallbackObject(), name, value, result->holder(), strict_mode); case INTERCEPTOR: return SetPropertyWithInterceptor(name, value, attributes, strict_mode); case CONSTANT_TRANSITION: { // If the same constant function is being added we can simply // transition to the target map. Map* target_map = result->GetTransitionMap(); DescriptorArray* target_descriptors = target_map->instance_descriptors(); int number = target_descriptors->SearchWithCache(name); ASSERT(number != DescriptorArray::kNotFound); ASSERT(target_descriptors->GetType(number) == CONSTANT_FUNCTION); JSFunction* function = JSFunction::cast(target_descriptors->GetValue(number)); if (value == function) { set_map(target_map); return value; } // Otherwise, replace with a MAP_TRANSITION to a new map with a // FIELD, even if the value is a constant function. return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); } case NULL_DESCRIPTOR: case ELEMENTS_TRANSITION: return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case HANDLER: UNREACHABLE(); return value; } UNREACHABLE(); // keep the compiler happy return value; } // Set a real local property, even if it is READ_ONLY. If the property is not // present, add it with attributes NONE. This code is an exact clone of // SetProperty, with the check for IsReadOnly and the check for a // callback setter removed. The two lines looking up the LookupResult // result are also added. If one of the functions is changed, the other // should be. // Note that this method cannot be used to set the prototype of a function // because ConvertDescriptorToField() which is called in "case CALLBACKS:" // doesn't handle function prototypes correctly. Handle JSObject::SetLocalPropertyIgnoreAttributes( Handle object, Handle key, Handle value, PropertyAttributes attributes) { CALL_HEAP_FUNCTION( object->GetIsolate(), object->SetLocalPropertyIgnoreAttributes(*key, *value, attributes), Object); } MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( String* name, Object* value, PropertyAttributes attributes) { // Make sure that the top context does not change when doing callbacks or // interceptor calls. AssertNoContextChange ncc; Isolate* isolate = GetIsolate(); LookupResult result(isolate); LocalLookup(name, &result); // Check access rights if needed. if (IsAccessCheckNeeded()) { if (!isolate->MayNamedAccess(this, name, v8::ACCESS_SET)) { return SetPropertyWithFailedAccessCheck(&result, name, value, false, kNonStrictMode); } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetLocalPropertyIgnoreAttributes( name, value, attributes); } // Check for accessor in prototype chain removed here in clone. if (!result.IsFound()) { // Neither properties nor transitions found. return AddProperty(name, value, attributes, kNonStrictMode); } PropertyDetails details = PropertyDetails(attributes, NORMAL); // Check of IsReadOnly removed from here in clone. switch (result.type()) { case NORMAL: return SetNormalizedProperty(name, value, details); case FIELD: return FastPropertyAtPut(result.GetFieldIndex(), value); case MAP_TRANSITION: if (attributes == result.GetAttributes()) { // Only use map transition if the attributes match. return AddFastPropertyUsingMap(result.GetTransitionMap(), name, value); } return ConvertDescriptorToField(name, value, attributes); case CONSTANT_FUNCTION: // Only replace the function if necessary. if (value == result.GetConstantFunction()) return value; // Preserve the attributes of this existing property. attributes = result.GetAttributes(); return ConvertDescriptorToField(name, value, attributes); case CALLBACKS: case INTERCEPTOR: // Override callback in clone return ConvertDescriptorToField(name, value, attributes); case CONSTANT_TRANSITION: // Replace with a MAP_TRANSITION to a new map with a FIELD, even // if the value is a function. return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case NULL_DESCRIPTOR: case ELEMENTS_TRANSITION: return ConvertDescriptorToFieldAndMapTransition(name, value, attributes); case HANDLER: UNREACHABLE(); return value; } UNREACHABLE(); // keep the compiler happy return value; } PropertyAttributes JSObject::GetPropertyAttributePostInterceptor( JSObject* receiver, String* name, bool continue_search) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsProperty()) return result.GetAttributes(); if (continue_search) { // Continue searching via the prototype chain. Object* pt = GetPrototype(); if (!pt->IsNull()) { return JSObject::cast(pt)-> GetPropertyAttributeWithReceiver(receiver, name); } } return ABSENT; } PropertyAttributes JSObject::GetPropertyAttributeWithInterceptor( JSObject* receiver, String* name, bool continue_search) { Isolate* isolate = GetIsolate(); // Make sure that the top context does not change when doing // callbacks or interceptor calls. AssertNoContextChange ncc; HandleScope scope(isolate); Handle interceptor(GetNamedInterceptor()); Handle receiver_handle(receiver); Handle holder_handle(this); Handle name_handle(name); CustomArguments args(isolate, interceptor->data(), receiver, this); v8::AccessorInfo info(args.end()); if (!interceptor->query()->IsUndefined()) { v8::NamedPropertyQuery query = v8::ToCData(interceptor->query()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-has", *holder_handle, name)); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = query(v8::Utils::ToLocal(name_handle), info); } if (!result.IsEmpty()) { ASSERT(result->IsInt32()); return static_cast(result->Int32Value()); } } else if (!interceptor->getter()->IsUndefined()) { v8::NamedPropertyGetter getter = v8::ToCData(interceptor->getter()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-get-has", this, name)); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = getter(v8::Utils::ToLocal(name_handle), info); } if (!result.IsEmpty()) return DONT_ENUM; } return holder_handle->GetPropertyAttributePostInterceptor(*receiver_handle, *name_handle, continue_search); } PropertyAttributes JSReceiver::GetPropertyAttributeWithReceiver( JSReceiver* receiver, String* key) { uint32_t index = 0; if (IsJSObject() && key->AsArrayIndex(&index)) { return JSObject::cast(this)->HasElementWithReceiver(receiver, index) ? NONE : ABSENT; } // Named property. LookupResult result(GetIsolate()); Lookup(key, &result); return GetPropertyAttribute(receiver, &result, key, true); } PropertyAttributes JSReceiver::GetPropertyAttribute(JSReceiver* receiver, LookupResult* result, String* name, bool continue_search) { // Check access rights if needed. if (IsAccessCheckNeeded()) { JSObject* this_obj = JSObject::cast(this); Heap* heap = GetHeap(); if (!heap->isolate()->MayNamedAccess(this_obj, name, v8::ACCESS_HAS)) { return this_obj->GetPropertyAttributeWithFailedAccessCheck( receiver, result, name, continue_search); } } if (result->IsProperty()) { switch (result->type()) { case NORMAL: // fall through case FIELD: case CONSTANT_FUNCTION: case CALLBACKS: return result->GetAttributes(); case HANDLER: { return JSProxy::cast(result->proxy())->GetPropertyAttributeWithHandler( receiver, name); } case INTERCEPTOR: return result->holder()->GetPropertyAttributeWithInterceptor( JSObject::cast(receiver), name, continue_search); default: UNREACHABLE(); } } return ABSENT; } PropertyAttributes JSReceiver::GetLocalPropertyAttribute(String* name) { // Check whether the name is an array index. uint32_t index = 0; if (IsJSObject() && name->AsArrayIndex(&index)) { if (JSObject::cast(this)->HasLocalElement(index)) return NONE; return ABSENT; } // Named property. LookupResult result(GetIsolate()); LocalLookup(name, &result); return GetPropertyAttribute(this, &result, name, false); } MaybeObject* NormalizedMapCache::Get(JSObject* obj, PropertyNormalizationMode mode) { Isolate* isolate = obj->GetIsolate(); Map* fast = obj->map(); int index = fast->Hash() % kEntries; Object* result = get(index); if (result->IsMap() && Map::cast(result)->EquivalentToForNormalization(fast, mode)) { #ifdef DEBUG if (FLAG_verify_heap) { Map::cast(result)->SharedMapVerify(); } if (FLAG_enable_slow_asserts) { // The cached map should match newly created normalized map bit-by-bit. Object* fresh; { MaybeObject* maybe_fresh = fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); if (maybe_fresh->ToObject(&fresh)) { ASSERT(memcmp(Map::cast(fresh)->address(), Map::cast(result)->address(), Map::kSize) == 0); } } } #endif return result; } { MaybeObject* maybe_result = fast->CopyNormalized(mode, SHARED_NORMALIZED_MAP); if (!maybe_result->ToObject(&result)) return maybe_result; } set(index, result); isolate->counters()->normalized_maps()->Increment(); return result; } void NormalizedMapCache::Clear() { int entries = length(); for (int i = 0; i != entries; i++) { set_undefined(i); } } void JSObject::UpdateMapCodeCache(Handle object, Handle name, Handle code) { Isolate* isolate = object->GetIsolate(); CALL_HEAP_FUNCTION_VOID(isolate, object->UpdateMapCodeCache(*name, *code)); } MaybeObject* JSObject::UpdateMapCodeCache(String* name, Code* code) { if (map()->is_shared()) { // Fast case maps are never marked as shared. ASSERT(!HasFastProperties()); // Replace the map with an identical copy that can be safely modified. Object* obj; { MaybeObject* maybe_obj = map()->CopyNormalized(KEEP_INOBJECT_PROPERTIES, UNIQUE_NORMALIZED_MAP); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } GetIsolate()->counters()->normalized_maps()->Increment(); set_map(Map::cast(obj)); } return map()->UpdateCodeCache(name, code); } void JSObject::NormalizeProperties(Handle object, PropertyNormalizationMode mode, int expected_additional_properties) { CALL_HEAP_FUNCTION_VOID(object->GetIsolate(), object->NormalizeProperties( mode, expected_additional_properties)); } MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, int expected_additional_properties) { if (!HasFastProperties()) return this; // The global object is always normalized. ASSERT(!IsGlobalObject()); // JSGlobalProxy must never be normalized ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); // Allocate new content. int property_count = map_of_this->NumberOfDescribedProperties(); if (expected_additional_properties > 0) { property_count += expected_additional_properties; } else { property_count += 2; // Make space for two more properties. } StringDictionary* dictionary; { MaybeObject* maybe_dictionary = StringDictionary::Allocate(property_count); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } DescriptorArray* descs = map_of_this->instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); switch (details.type()) { case CONSTANT_FUNCTION: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = descs->GetConstantFunction(i); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case FIELD: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = FastPropertyAt(descs->GetFieldIndex(i)); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case CALLBACKS: { if (!descs->IsProperty(i)) break; Object* value = descs->GetCallbacksObject(i); if (value->IsAccessorPair()) { MaybeObject* maybe_copy = AccessorPair::cast(value)->CopyWithoutTransitions(); if (!maybe_copy->To(&value)) return maybe_copy; } MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case MAP_TRANSITION: case CONSTANT_TRANSITION: case NULL_DESCRIPTOR: case INTERCEPTOR: case ELEMENTS_TRANSITION: break; case HANDLER: case NORMAL: UNREACHABLE(); break; } } Heap* current_heap = GetHeap(); // Copy the next enumeration index from instance descriptor. int index = map_of_this->instance_descriptors()->NextEnumerationIndex(); dictionary->SetNextEnumerationIndex(index); Map* new_map; { MaybeObject* maybe_map = current_heap->isolate()->context()->global_context()-> normalized_map_cache()->Get(this, mode); if (!maybe_map->To(&new_map)) return maybe_map; } // We have now successfully allocated all the necessary objects. // Changes can now be made with the guarantee that all of them take effect. // Resize the object in the heap if necessary. int new_instance_size = new_map->instance_size(); int instance_size_delta = map_of_this->instance_size() - new_instance_size; ASSERT(instance_size_delta >= 0); current_heap->CreateFillerObjectAt(this->address() + new_instance_size, instance_size_delta); if (Marking::IsBlack(Marking::MarkBitFrom(this))) { MemoryChunk::IncrementLiveBytesFromMutator(this->address(), -instance_size_delta); } set_map(new_map); new_map->clear_instance_descriptors(); set_properties(dictionary); current_heap->isolate()->counters()->props_to_dictionary()->Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object properties have been normalized:\n"); Print(); } #endif return this; } void JSObject::TransformToFastProperties(Handle object, int unused_property_fields) { CALL_HEAP_FUNCTION_VOID( object->GetIsolate(), object->TransformToFastProperties(unused_property_fields)); } MaybeObject* JSObject::TransformToFastProperties(int unused_property_fields) { if (HasFastProperties()) return this; ASSERT(!IsGlobalObject()); return property_dictionary()-> TransformPropertiesToFastFor(this, unused_property_fields); } Handle JSObject::NormalizeElements( Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->NormalizeElements(), SeededNumberDictionary); } MaybeObject* JSObject::NormalizeElements() { ASSERT(!HasExternalArrayElements()); // Find the backing store. FixedArrayBase* array = FixedArrayBase::cast(elements()); Map* old_map = array->map(); bool is_arguments = (old_map == old_map->GetHeap()->non_strict_arguments_elements_map()); if (is_arguments) { array = FixedArrayBase::cast(FixedArray::cast(array)->get(1)); } if (array->IsDictionary()) return array; ASSERT(HasFastElements() || HasFastSmiOnlyElements() || HasFastDoubleElements() || HasFastArgumentsElements()); // Compute the effective length and allocate a new backing store. int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : array->length(); int old_capacity = 0; int used_elements = 0; GetElementsCapacityAndUsage(&old_capacity, &used_elements); SeededNumberDictionary* dictionary = NULL; { Object* object; MaybeObject* maybe = SeededNumberDictionary::Allocate(used_elements); if (!maybe->ToObject(&object)) return maybe; dictionary = SeededNumberDictionary::cast(object); } // Copy the elements to the new backing store. bool has_double_elements = array->IsFixedDoubleArray(); for (int i = 0; i < length; i++) { Object* value = NULL; if (has_double_elements) { FixedDoubleArray* double_array = FixedDoubleArray::cast(array); if (double_array->is_the_hole(i)) { value = GetIsolate()->heap()->the_hole_value(); } else { // Objects must be allocated in the old object space, since the // overall number of HeapNumbers needed for the conversion might // exceed the capacity of new space, and we would fail repeatedly // trying to convert the FixedDoubleArray. MaybeObject* maybe_value_object = GetHeap()->AllocateHeapNumber(double_array->get_scalar(i), TENURED); if (!maybe_value_object->ToObject(&value)) return maybe_value_object; } } else { ASSERT(old_map->has_fast_elements() || old_map->has_fast_smi_only_elements()); value = FixedArray::cast(array)->get(i); } PropertyDetails details = PropertyDetails(NONE, NORMAL); if (!value->IsTheHole()) { Object* result; MaybeObject* maybe_result = dictionary->AddNumberEntry(i, value, details); if (!maybe_result->ToObject(&result)) return maybe_result; dictionary = SeededNumberDictionary::cast(result); } } // Switch to using the dictionary as the backing storage for elements. if (is_arguments) { FixedArray::cast(elements())->set(1, dictionary); } else { // Set the new map first to satify the elements type assert in // set_elements(). Object* new_map; MaybeObject* maybe = GetElementsTransitionMap(GetIsolate(), DICTIONARY_ELEMENTS); if (!maybe->ToObject(&new_map)) return maybe; set_map(Map::cast(new_map)); set_elements(dictionary); } old_map->GetHeap()->isolate()->counters()->elements_to_dictionary()-> Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object elements have been normalized:\n"); Print(); } #endif ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); return dictionary; } Smi* JSReceiver::GenerateIdentityHash() { Isolate* isolate = GetIsolate(); int hash_value; int attempts = 0; do { // Generate a random 32-bit hash value but limit range to fit // within a smi. hash_value = V8::RandomPrivate(isolate) & Smi::kMaxValue; attempts++; } while (hash_value == 0 && attempts < 30); hash_value = hash_value != 0 ? hash_value : 1; // never return 0 return Smi::FromInt(hash_value); } MaybeObject* JSObject::SetIdentityHash(Object* hash, CreationFlag flag) { MaybeObject* maybe = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (maybe->IsFailure()) return maybe; return this; } int JSObject::GetIdentityHash(Handle obj) { CALL_AND_RETRY(obj->GetIsolate(), obj->GetIdentityHash(ALLOW_CREATION), return Smi::cast(__object__)->value(), return 0); } MaybeObject* JSObject::GetIdentityHash(CreationFlag flag) { Object* stored_value = GetHiddenProperty(GetHeap()->identity_hash_symbol()); if (stored_value->IsSmi()) return stored_value; // Do not generate permanent identity hash code if not requested. if (flag == OMIT_CREATION) return GetHeap()->undefined_value(); Smi* hash = GenerateIdentityHash(); MaybeObject* result = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (result->IsFailure()) return result; if (result->ToObjectUnchecked()->IsUndefined()) { // Trying to get hash of detached proxy. return Smi::FromInt(0); } return hash; } MaybeObject* JSProxy::GetIdentityHash(CreationFlag flag) { Object* hash = this->hash(); if (!hash->IsSmi() && flag == ALLOW_CREATION) { hash = GenerateIdentityHash(); set_hash(hash); } return hash; } Object* JSObject::GetHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->GetHiddenProperty(key); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) { return GetHeap()->undefined_value(); } StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) return GetHeap()->undefined_value(); return dictionary->ValueAt(entry); } Handle JSObject::SetHiddenProperty(Handle obj, Handle key, Handle value) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->SetHiddenProperty(*key, *value), Object); } MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->SetHiddenProperty(key, value); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(true); StringDictionary* dictionary; if (!hidden_lookup->To(&dictionary)) return hidden_lookup; // If it was found, check if the key is already in the dictionary. int entry = dictionary->FindEntry(key); if (entry != StringDictionary::kNotFound) { // If key was found, just update the value. dictionary->ValueAtPut(entry, value); return this; } // Key was not already in the dictionary, so add the entry. MaybeObject* insert_result = dictionary->Add(key, value, PropertyDetails(NONE, NORMAL)); StringDictionary* new_dict; if (!insert_result->To(&new_dict)) return insert_result; if (new_dict != dictionary) { // If adding the key expanded the dictionary (i.e., Add returned a new // dictionary), store it back to the object. MaybeObject* store_result = SetHiddenPropertiesDictionary(new_dict); if (store_result->IsFailure()) return store_result; } // Return this to mark success. return this; } void JSObject::DeleteHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return immediately. if (proxy_parent->IsNull()) return; ASSERT(proxy_parent->IsJSGlobalObject()); JSObject::cast(proxy_parent)->DeleteHiddenProperty(key); return; } MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) return; StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) { // Key wasn't in dictionary. Deletion is a success. return; } // Key was in the dictionary. Remove it. dictionary->DeleteProperty(entry, JSReceiver::FORCE_DELETION); } bool JSObject::HasHiddenProperties() { return GetPropertyAttributePostInterceptor(this, GetHeap()->hidden_symbol(), false) != ABSENT; } MaybeObject* JSObject::GetHiddenPropertiesDictionary(bool create_if_absent) { ASSERT(!IsJSGlobalProxy()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { Object* hidden_store = this->FastPropertyAt(descriptors->GetFieldIndex(0)); return StringDictionary::cast(hidden_store); } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } else { PropertyAttributes attributes; // You can't install a getter on a property indexed by the hidden symbol, // so we can be sure that GetLocalPropertyPostInterceptor returns a real // object. Object* lookup = GetLocalPropertyPostInterceptor(this, GetHeap()->hidden_symbol(), &attributes)->ToObjectUnchecked(); if (!lookup->IsUndefined()) { return StringDictionary::cast(lookup); } } if (!create_if_absent) return GetHeap()->undefined_value(); const int kInitialSize = 5; MaybeObject* dict_alloc = StringDictionary::Allocate(kInitialSize); StringDictionary* dictionary; if (!dict_alloc->To(&dictionary)) return dict_alloc; MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return dictionary; } MaybeObject* JSObject::SetHiddenPropertiesDictionary( StringDictionary* dictionary) { ASSERT(!IsJSGlobalProxy()); ASSERT(HasHiddenProperties()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { this->FastPropertyAtPut(descriptors->GetFieldIndex(0), dictionary); return this; } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return this; } MaybeObject* JSObject::DeletePropertyPostInterceptor(String* name, DeleteMode mode) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (!result.IsProperty()) return GetHeap()->true_value(); // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return DeleteNormalizedProperty(name, mode); } MaybeObject* JSObject::DeletePropertyWithInterceptor(String* name) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle interceptor(GetNamedInterceptor()); Handle name_handle(name); Handle this_handle(this); if (!interceptor->deleter()->IsUndefined()) { v8::NamedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-delete", *this_handle, name)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(v8::Utils::ToLocal(name_handle), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } } MaybeObject* raw_result = this_handle->DeletePropertyPostInterceptor(*name_handle, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } MaybeObject* JSObject::DeleteElementWithInterceptor(uint32_t index) { Isolate* isolate = GetIsolate(); Heap* heap = isolate->heap(); // Make sure that the top context does not change when doing // callbacks or interceptor calls. AssertNoContextChange ncc; HandleScope scope(isolate); Handle interceptor(GetIndexedInterceptor()); if (interceptor->deleter()->IsUndefined()) return heap->false_value(); v8::IndexedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); Handle this_handle(this); LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-delete", this, index)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(index, info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } MaybeObject* raw_result = this_handle->GetElementsAccessor()->Delete( *this_handle, index, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } Handle JSObject::DeleteElement(Handle obj, uint32_t index) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteElement(index, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { Isolate* isolate = GetIsolate(); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayIndexedAccess(this, index, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteElement(index, mode); } if (HasIndexedInterceptor()) { // Skip interceptor if forcing deletion. if (mode != FORCE_DELETION) { return DeleteElementWithInterceptor(index); } mode = JSReceiver::FORCE_DELETION; } return GetElementsAccessor()->Delete(this, index, mode); } Handle JSObject::DeleteProperty(Handle obj, Handle prop) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteProperty(*prop, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) { Isolate* isolate = GetIsolate(); // ECMA-262, 3rd, 8.6.2.5 ASSERT(name->IsString()); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, name, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteProperty(name, mode); } uint32_t index = 0; if (name->AsArrayIndex(&index)) { return DeleteElement(index, mode); } else { LookupResult result(isolate); LocalLookup(name, &result); if (!result.IsProperty()) return isolate->heap()->true_value(); // Ignore attributes if forcing a deletion. if (result.IsDontDelete() && mode != FORCE_DELETION) { if (mode == STRICT_DELETION) { // Deleting a non-configurable property in strict mode. HandleScope scope(isolate); Handle args[2] = { Handle(name), Handle(this) }; return isolate->Throw(*isolate->factory()->NewTypeError( "strict_delete_property", HandleVector(args, 2))); } return isolate->heap()->false_value(); } // Check for interceptor. if (result.type() == INTERCEPTOR) { // Skip interceptor if forcing a deletion. if (mode == FORCE_DELETION) { return DeletePropertyPostInterceptor(name, mode); } return DeletePropertyWithInterceptor(name); } // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } // Make sure the properties are normalized before removing the entry. return DeleteNormalizedProperty(name, mode); } } MaybeObject* JSReceiver::DeleteElement(uint32_t index, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeleteElementWithHandler(index, mode); } return JSObject::cast(this)->DeleteElement(index, mode); } MaybeObject* JSReceiver::DeleteProperty(String* name, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeletePropertyWithHandler(name, mode); } return JSObject::cast(this)->DeleteProperty(name, mode); } bool JSObject::ReferencesObjectFromElements(FixedArray* elements, ElementsKind kind, Object* object) { ASSERT(kind == FAST_ELEMENTS || kind == DICTIONARY_ELEMENTS); if (kind == FAST_ELEMENTS) { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : elements->length(); for (int i = 0; i < length; ++i) { Object* element = elements->get(i); if (!element->IsTheHole() && element == object) return true; } } else { Object* key = SeededNumberDictionary::cast(elements)->SlowReverseLookup(object); if (!key->IsUndefined()) return true; } return false; } // Check whether this object references another object. bool JSObject::ReferencesObject(Object* obj) { Map* map_of_this = map(); Heap* heap = GetHeap(); AssertNoAllocation no_alloc; // Is the object the constructor for this object? if (map_of_this->constructor() == obj) { return true; } // Is the object the prototype for this object? if (map_of_this->prototype() == obj) { return true; } // Check if the object is among the named properties. Object* key = SlowReverseLookup(obj); if (!key->IsUndefined()) { return true; } // Check if the object is among the indexed properties. ElementsKind kind = GetElementsKind(); switch (kind) { case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: case FAST_DOUBLE_ELEMENTS: // Raw pixels and external arrays do not reference other // objects. break; case FAST_SMI_ONLY_ELEMENTS: break; case FAST_ELEMENTS: case DICTIONARY_ELEMENTS: { FixedArray* elements = FixedArray::cast(this->elements()); if (ReferencesObjectFromElements(elements, kind, obj)) return true; break; } case NON_STRICT_ARGUMENTS_ELEMENTS: { FixedArray* parameter_map = FixedArray::cast(elements()); // Check the mapped parameters. int length = parameter_map->length(); for (int i = 2; i < length; ++i) { Object* value = parameter_map->get(i); if (!value->IsTheHole() && value == obj) return true; } // Check the arguments. FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); kind = arguments->IsDictionary() ? DICTIONARY_ELEMENTS : FAST_ELEMENTS; if (ReferencesObjectFromElements(arguments, kind, obj)) return true; break; } } // For functions check the context. if (IsJSFunction()) { // Get the constructor function for arguments array. JSObject* arguments_boilerplate = heap->isolate()->context()->global_context()-> arguments_boilerplate(); JSFunction* arguments_function = JSFunction::cast(arguments_boilerplate->map()->constructor()); // Get the context and don't check if it is the global context. JSFunction* f = JSFunction::cast(this); Context* context = f->context(); if (context->IsGlobalContext()) { return false; } // Check the non-special context slots. for (int i = Context::MIN_CONTEXT_SLOTS; i < context->length(); i++) { // Only check JS objects. if (context->get(i)->IsJSObject()) { JSObject* ctxobj = JSObject::cast(context->get(i)); // If it is an arguments array check the content. if (ctxobj->map()->constructor() == arguments_function) { if (ctxobj->ReferencesObject(obj)) { return true; } } else if (ctxobj == obj) { return true; } } } // Check the context extension (if any) if it can have references. if (context->has_extension() && !context->IsCatchContext()) { return JSObject::cast(context->extension())->ReferencesObject(obj); } } // No references to object. return false; } Handle JSObject::PreventExtensions(Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->PreventExtensions(), Object); } MaybeObject* JSObject::PreventExtensions() { Isolate* isolate = GetIsolate(); if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, isolate->heap()->undefined_value(), v8::ACCESS_KEYS)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_KEYS); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return this; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->PreventExtensions(); } // It's not possible to seal objects with external array elements if (HasExternalArrayElements()) { HandleScope scope(isolate); Handle object(this); Handle error = isolate->factory()->NewTypeError( "cant_prevent_ext_external_array_elements", HandleVector(&object, 1)); return isolate->Throw(*error); } // If there are fast elements we normalize. SeededNumberDictionary* dictionary = NULL; { MaybeObject* maybe = NormalizeElements(); if (!maybe->To(&dictionary)) return maybe; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Make sure that we never go back to fast case. dictionary->set_requires_slow_elements(); // Do a map transition, other objects with this map may still // be extensible. Map* new_map; { MaybeObject* maybe = map()->CopyDropTransitions(); if (!maybe->To(&new_map)) return maybe; } new_map->set_is_extensible(false); set_map(new_map); ASSERT(!map()->is_extensible()); return new_map; } // Tests for the fast common case for property enumeration: // - This object and all prototypes has an enum cache (which means that // it is no proxy, has no interceptors and needs no access checks). // - This object has no elements. // - No prototype has enumerable properties/elements. bool JSReceiver::IsSimpleEnum() { Heap* heap = GetHeap(); for (Object* o = this; o != heap->null_value(); o = JSObject::cast(o)->GetPrototype()) { if (!o->IsJSObject()) return false; JSObject* curr = JSObject::cast(o); if (!curr->map()->instance_descriptors()->HasEnumCache()) return false; ASSERT(!curr->HasNamedInterceptor()); ASSERT(!curr->HasIndexedInterceptor()); ASSERT(!curr->IsAccessCheckNeeded()); if (curr->NumberOfEnumElements() > 0) return false; if (curr != this) { FixedArray* curr_fixed_array = FixedArray::cast(curr->map()->instance_descriptors()->GetEnumCache()); if (curr_fixed_array->length() > 0) return false; } } return true; } int Map::NumberOfDescribedProperties(PropertyAttributes filter) { int result = 0; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); if (descs->IsProperty(i) && (details.attributes() & filter) == 0) { result++; } } return result; } int Map::PropertyIndexFor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && !descs->IsNullDescriptor(i)) { return descs->GetFieldIndex(i); } } return -1; } int Map::NextFreePropertyIndex() { int max_index = -1; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (descs->GetType(i) == FIELD) { int current_index = descs->GetFieldIndex(i); if (current_index > max_index) max_index = current_index; } } return max_index + 1; } AccessorDescriptor* Map::FindAccessor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && descs->GetType(i) == CALLBACKS) { return descs->GetCallbacks(i); } } return NULL; } void JSReceiver::LocalLookup(String* name, LookupResult* result) { ASSERT(name->IsString()); Heap* heap = GetHeap(); if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); return JSReceiver::cast(proto)->LocalLookup(name, result); } if (IsJSProxy()) { result->HandlerResult(JSProxy::cast(this)); return; } // Do not use inline caching if the object is a non-global object // that requires access checks. if (IsAccessCheckNeeded()) { result->DisallowCaching(); } JSObject* js_object = JSObject::cast(this); // Check __proto__ before interceptor. if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject()) { result->ConstantResult(js_object); return; } // Check for lookup interceptor except when bootstrapping. if (js_object->HasNamedInterceptor() && !heap->isolate()->bootstrapper()->IsActive()) { result->InterceptorResult(js_object); return; } js_object->LocalLookupRealNamedProperty(name, result); } void JSReceiver::Lookup(String* name, LookupResult* result) { // Ecma-262 3rd 8.6.2.4 Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value(); current = JSObject::cast(current)->GetPrototype()) { JSReceiver::cast(current)->LocalLookup(name, result); if (result->IsProperty()) return; } result->NotFound(); } // Search object and it's prototype chain for callback properties. void JSObject::LookupCallback(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value() && current->IsJSObject(); current = JSObject::cast(current)->GetPrototype()) { JSObject::cast(current)->LocalLookupRealNamedProperty(name, result); if (result->IsFound() && result->type() == CALLBACKS) return; } result->NotFound(); } // Try to update an accessor in an elements dictionary. Return true if the // update succeeded, and false otherwise. static bool UpdateGetterSetterInDictionary( SeededNumberDictionary* dictionary, uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { Object* result = dictionary->ValueAt(entry); PropertyDetails details = dictionary->DetailsAt(entry); if (details.type() == CALLBACKS && result->IsAccessorPair()) { ASSERT(!details.IsDontDelete()); if (details.attributes() != attributes) { dictionary->DetailsAtPut(entry, PropertyDetails(attributes, CALLBACKS, index)); } AccessorPair::cast(result)->SetComponents(getter, setter); return true; } } return false; } MaybeObject* JSObject::DefineElementAccessor(uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { switch (GetElementsKind()) { case FAST_SMI_ONLY_ELEMENTS: case FAST_ELEMENTS: case FAST_DOUBLE_ELEMENTS: break; case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: // Ignore getters and setters on pixel and external array elements. return GetHeap()->undefined_value(); case DICTIONARY_ELEMENTS: if (UpdateGetterSetterInDictionary(element_dictionary(), index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } break; case NON_STRICT_ARGUMENTS_ELEMENTS: { // Ascertain whether we have read-only properties or an existing // getter/setter pair in an arguments elements dictionary backing // store. FixedArray* parameter_map = FixedArray::cast(elements()); uint32_t length = parameter_map->length(); Object* probe = index < (length - 2) ? parameter_map->get(index + 2) : NULL; if (probe == NULL || probe->IsTheHole()) { FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); if (arguments->IsDictionary()) { SeededNumberDictionary* dictionary = SeededNumberDictionary::cast(arguments); if (UpdateGetterSetterInDictionary(dictionary, index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } } } break; } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetElementCallback(index, accessors, attributes); } MaybeObject* JSObject::DefinePropertyAccessor(String* name, Object* getter, Object* setter, PropertyAttributes attributes) { // Lookup the name. LookupResult result(GetHeap()->isolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsFound()) { if (result.type() == CALLBACKS) { ASSERT(!result.IsDontDelete()); Object* obj = result.GetCallbackObject(); // Need to preserve old getters/setters. if (obj->IsAccessorPair()) { AccessorPair* copy; { MaybeObject* maybe_copy = AccessorPair::cast(obj)->CopyWithoutTransitions(); if (!maybe_copy->To(©)) return maybe_copy; } copy->SetComponents(getter, setter); // Use set to update attributes. return SetPropertyCallback(name, copy, attributes); } } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetPropertyCallback(name, accessors, attributes); } bool JSObject::CanSetCallback(String* name) { ASSERT(!IsAccessCheckNeeded() || GetIsolate()->MayNamedAccess(this, name, v8::ACCESS_SET)); // Check if there is an API defined callback object which prohibits // callback overwriting in this object or it's prototype chain. // This mechanism is needed for instance in a browser setting, where // certain accessors such as window.location should not be allowed // to be overwritten because allowing overwriting could potentially // cause security problems. LookupResult callback_result(GetIsolate()); LookupCallback(name, &callback_result); if (callback_result.IsProperty()) { Object* obj = callback_result.GetCallbackObject(); if (obj->IsAccessorInfo() && AccessorInfo::cast(obj)->prohibits_overwriting()) { return false; } } return true; } MaybeObject* JSObject::SetElementCallback(uint32_t index, Object* structure, PropertyAttributes attributes) { PropertyDetails details = PropertyDetails(attributes, CALLBACKS); // Normalize elements to make this operation simple. SeededNumberDictionary* dictionary; { MaybeObject* maybe_dictionary = NormalizeElements(); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Update the dictionary with the new CALLBACKS property. { MaybeObject* maybe_dictionary = dictionary->Set(index, structure, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } dictionary->set_requires_slow_elements(); // Update the dictionary backing store on the object. if (elements()->map() == GetHeap()->non_strict_arguments_elements_map()) { // Also delete any parameter alias. // // TODO(kmillikin): when deleting the last parameter alias we could // switch to a direct backing store without the parameter map. This // would allow GC of the context. FixedArray* parameter_map = FixedArray::cast(elements()); if (index < static_cast
code) { Isolate* isolate = object->GetIsolate(); CALL_HEAP_FUNCTION_VOID(isolate, object->UpdateMapCodeCache(*name, *code)); } MaybeObject* JSObject::UpdateMapCodeCache(String* name, Code* code) { if (map()->is_shared()) { // Fast case maps are never marked as shared. ASSERT(!HasFastProperties()); // Replace the map with an identical copy that can be safely modified. Object* obj; { MaybeObject* maybe_obj = map()->CopyNormalized(KEEP_INOBJECT_PROPERTIES, UNIQUE_NORMALIZED_MAP); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } GetIsolate()->counters()->normalized_maps()->Increment(); set_map(Map::cast(obj)); } return map()->UpdateCodeCache(name, code); } void JSObject::NormalizeProperties(Handle object, PropertyNormalizationMode mode, int expected_additional_properties) { CALL_HEAP_FUNCTION_VOID(object->GetIsolate(), object->NormalizeProperties( mode, expected_additional_properties)); } MaybeObject* JSObject::NormalizeProperties(PropertyNormalizationMode mode, int expected_additional_properties) { if (!HasFastProperties()) return this; // The global object is always normalized. ASSERT(!IsGlobalObject()); // JSGlobalProxy must never be normalized ASSERT(!IsJSGlobalProxy()); Map* map_of_this = map(); // Allocate new content. int property_count = map_of_this->NumberOfDescribedProperties(); if (expected_additional_properties > 0) { property_count += expected_additional_properties; } else { property_count += 2; // Make space for two more properties. } StringDictionary* dictionary; { MaybeObject* maybe_dictionary = StringDictionary::Allocate(property_count); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } DescriptorArray* descs = map_of_this->instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); switch (details.type()) { case CONSTANT_FUNCTION: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = descs->GetConstantFunction(i); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case FIELD: { PropertyDetails d = PropertyDetails(details.attributes(), NORMAL, details.index()); Object* value = FastPropertyAt(descs->GetFieldIndex(i)); MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, d); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case CALLBACKS: { if (!descs->IsProperty(i)) break; Object* value = descs->GetCallbacksObject(i); if (value->IsAccessorPair()) { MaybeObject* maybe_copy = AccessorPair::cast(value)->CopyWithoutTransitions(); if (!maybe_copy->To(&value)) return maybe_copy; } MaybeObject* maybe_dictionary = dictionary->Add(descs->GetKey(i), value, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; break; } case MAP_TRANSITION: case CONSTANT_TRANSITION: case NULL_DESCRIPTOR: case INTERCEPTOR: case ELEMENTS_TRANSITION: break; case HANDLER: case NORMAL: UNREACHABLE(); break; } } Heap* current_heap = GetHeap(); // Copy the next enumeration index from instance descriptor. int index = map_of_this->instance_descriptors()->NextEnumerationIndex(); dictionary->SetNextEnumerationIndex(index); Map* new_map; { MaybeObject* maybe_map = current_heap->isolate()->context()->global_context()-> normalized_map_cache()->Get(this, mode); if (!maybe_map->To(&new_map)) return maybe_map; } // We have now successfully allocated all the necessary objects. // Changes can now be made with the guarantee that all of them take effect. // Resize the object in the heap if necessary. int new_instance_size = new_map->instance_size(); int instance_size_delta = map_of_this->instance_size() - new_instance_size; ASSERT(instance_size_delta >= 0); current_heap->CreateFillerObjectAt(this->address() + new_instance_size, instance_size_delta); if (Marking::IsBlack(Marking::MarkBitFrom(this))) { MemoryChunk::IncrementLiveBytesFromMutator(this->address(), -instance_size_delta); } set_map(new_map); new_map->clear_instance_descriptors(); set_properties(dictionary); current_heap->isolate()->counters()->props_to_dictionary()->Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object properties have been normalized:\n"); Print(); } #endif return this; } void JSObject::TransformToFastProperties(Handle object, int unused_property_fields) { CALL_HEAP_FUNCTION_VOID( object->GetIsolate(), object->TransformToFastProperties(unused_property_fields)); } MaybeObject* JSObject::TransformToFastProperties(int unused_property_fields) { if (HasFastProperties()) return this; ASSERT(!IsGlobalObject()); return property_dictionary()-> TransformPropertiesToFastFor(this, unused_property_fields); } Handle JSObject::NormalizeElements( Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->NormalizeElements(), SeededNumberDictionary); } MaybeObject* JSObject::NormalizeElements() { ASSERT(!HasExternalArrayElements()); // Find the backing store. FixedArrayBase* array = FixedArrayBase::cast(elements()); Map* old_map = array->map(); bool is_arguments = (old_map == old_map->GetHeap()->non_strict_arguments_elements_map()); if (is_arguments) { array = FixedArrayBase::cast(FixedArray::cast(array)->get(1)); } if (array->IsDictionary()) return array; ASSERT(HasFastElements() || HasFastSmiOnlyElements() || HasFastDoubleElements() || HasFastArgumentsElements()); // Compute the effective length and allocate a new backing store. int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : array->length(); int old_capacity = 0; int used_elements = 0; GetElementsCapacityAndUsage(&old_capacity, &used_elements); SeededNumberDictionary* dictionary = NULL; { Object* object; MaybeObject* maybe = SeededNumberDictionary::Allocate(used_elements); if (!maybe->ToObject(&object)) return maybe; dictionary = SeededNumberDictionary::cast(object); } // Copy the elements to the new backing store. bool has_double_elements = array->IsFixedDoubleArray(); for (int i = 0; i < length; i++) { Object* value = NULL; if (has_double_elements) { FixedDoubleArray* double_array = FixedDoubleArray::cast(array); if (double_array->is_the_hole(i)) { value = GetIsolate()->heap()->the_hole_value(); } else { // Objects must be allocated in the old object space, since the // overall number of HeapNumbers needed for the conversion might // exceed the capacity of new space, and we would fail repeatedly // trying to convert the FixedDoubleArray. MaybeObject* maybe_value_object = GetHeap()->AllocateHeapNumber(double_array->get_scalar(i), TENURED); if (!maybe_value_object->ToObject(&value)) return maybe_value_object; } } else { ASSERT(old_map->has_fast_elements() || old_map->has_fast_smi_only_elements()); value = FixedArray::cast(array)->get(i); } PropertyDetails details = PropertyDetails(NONE, NORMAL); if (!value->IsTheHole()) { Object* result; MaybeObject* maybe_result = dictionary->AddNumberEntry(i, value, details); if (!maybe_result->ToObject(&result)) return maybe_result; dictionary = SeededNumberDictionary::cast(result); } } // Switch to using the dictionary as the backing storage for elements. if (is_arguments) { FixedArray::cast(elements())->set(1, dictionary); } else { // Set the new map first to satify the elements type assert in // set_elements(). Object* new_map; MaybeObject* maybe = GetElementsTransitionMap(GetIsolate(), DICTIONARY_ELEMENTS); if (!maybe->ToObject(&new_map)) return maybe; set_map(Map::cast(new_map)); set_elements(dictionary); } old_map->GetHeap()->isolate()->counters()->elements_to_dictionary()-> Increment(); #ifdef DEBUG if (FLAG_trace_normalization) { PrintF("Object elements have been normalized:\n"); Print(); } #endif ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); return dictionary; } Smi* JSReceiver::GenerateIdentityHash() { Isolate* isolate = GetIsolate(); int hash_value; int attempts = 0; do { // Generate a random 32-bit hash value but limit range to fit // within a smi. hash_value = V8::RandomPrivate(isolate) & Smi::kMaxValue; attempts++; } while (hash_value == 0 && attempts < 30); hash_value = hash_value != 0 ? hash_value : 1; // never return 0 return Smi::FromInt(hash_value); } MaybeObject* JSObject::SetIdentityHash(Object* hash, CreationFlag flag) { MaybeObject* maybe = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (maybe->IsFailure()) return maybe; return this; } int JSObject::GetIdentityHash(Handle obj) { CALL_AND_RETRY(obj->GetIsolate(), obj->GetIdentityHash(ALLOW_CREATION), return Smi::cast(__object__)->value(), return 0); } MaybeObject* JSObject::GetIdentityHash(CreationFlag flag) { Object* stored_value = GetHiddenProperty(GetHeap()->identity_hash_symbol()); if (stored_value->IsSmi()) return stored_value; // Do not generate permanent identity hash code if not requested. if (flag == OMIT_CREATION) return GetHeap()->undefined_value(); Smi* hash = GenerateIdentityHash(); MaybeObject* result = SetHiddenProperty(GetHeap()->identity_hash_symbol(), hash); if (result->IsFailure()) return result; if (result->ToObjectUnchecked()->IsUndefined()) { // Trying to get hash of detached proxy. return Smi::FromInt(0); } return hash; } MaybeObject* JSProxy::GetIdentityHash(CreationFlag flag) { Object* hash = this->hash(); if (!hash->IsSmi() && flag == ALLOW_CREATION) { hash = GenerateIdentityHash(); set_hash(hash); } return hash; } Object* JSObject::GetHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->GetHiddenProperty(key); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) { return GetHeap()->undefined_value(); } StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) return GetHeap()->undefined_value(); return dictionary->ValueAt(entry); } Handle JSObject::SetHiddenProperty(Handle obj, Handle key, Handle value) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->SetHiddenProperty(*key, *value), Object); } MaybeObject* JSObject::SetHiddenProperty(String* key, Object* value) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return undefined. if (proxy_parent->IsNull()) return GetHeap()->undefined_value(); ASSERT(proxy_parent->IsJSGlobalObject()); return JSObject::cast(proxy_parent)->SetHiddenProperty(key, value); } ASSERT(!IsJSGlobalProxy()); MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(true); StringDictionary* dictionary; if (!hidden_lookup->To(&dictionary)) return hidden_lookup; // If it was found, check if the key is already in the dictionary. int entry = dictionary->FindEntry(key); if (entry != StringDictionary::kNotFound) { // If key was found, just update the value. dictionary->ValueAtPut(entry, value); return this; } // Key was not already in the dictionary, so add the entry. MaybeObject* insert_result = dictionary->Add(key, value, PropertyDetails(NONE, NORMAL)); StringDictionary* new_dict; if (!insert_result->To(&new_dict)) return insert_result; if (new_dict != dictionary) { // If adding the key expanded the dictionary (i.e., Add returned a new // dictionary), store it back to the object. MaybeObject* store_result = SetHiddenPropertiesDictionary(new_dict); if (store_result->IsFailure()) return store_result; } // Return this to mark success. return this; } void JSObject::DeleteHiddenProperty(String* key) { if (IsJSGlobalProxy()) { // For a proxy, use the prototype as target object. Object* proxy_parent = GetPrototype(); // If the proxy is detached, return immediately. if (proxy_parent->IsNull()) return; ASSERT(proxy_parent->IsJSGlobalObject()); JSObject::cast(proxy_parent)->DeleteHiddenProperty(key); return; } MaybeObject* hidden_lookup = GetHiddenPropertiesDictionary(false); ASSERT(!hidden_lookup->IsFailure()); // No failure when passing false as arg. if (hidden_lookup->ToObjectUnchecked()->IsUndefined()) return; StringDictionary* dictionary = StringDictionary::cast(hidden_lookup->ToObjectUnchecked()); int entry = dictionary->FindEntry(key); if (entry == StringDictionary::kNotFound) { // Key wasn't in dictionary. Deletion is a success. return; } // Key was in the dictionary. Remove it. dictionary->DeleteProperty(entry, JSReceiver::FORCE_DELETION); } bool JSObject::HasHiddenProperties() { return GetPropertyAttributePostInterceptor(this, GetHeap()->hidden_symbol(), false) != ABSENT; } MaybeObject* JSObject::GetHiddenPropertiesDictionary(bool create_if_absent) { ASSERT(!IsJSGlobalProxy()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { Object* hidden_store = this->FastPropertyAt(descriptors->GetFieldIndex(0)); return StringDictionary::cast(hidden_store); } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } else { PropertyAttributes attributes; // You can't install a getter on a property indexed by the hidden symbol, // so we can be sure that GetLocalPropertyPostInterceptor returns a real // object. Object* lookup = GetLocalPropertyPostInterceptor(this, GetHeap()->hidden_symbol(), &attributes)->ToObjectUnchecked(); if (!lookup->IsUndefined()) { return StringDictionary::cast(lookup); } } if (!create_if_absent) return GetHeap()->undefined_value(); const int kInitialSize = 5; MaybeObject* dict_alloc = StringDictionary::Allocate(kInitialSize); StringDictionary* dictionary; if (!dict_alloc->To(&dictionary)) return dict_alloc; MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return dictionary; } MaybeObject* JSObject::SetHiddenPropertiesDictionary( StringDictionary* dictionary) { ASSERT(!IsJSGlobalProxy()); ASSERT(HasHiddenProperties()); if (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 (and no other string has hash // code zero) it will always occupy the first entry if present. DescriptorArray* descriptors = this->map()->instance_descriptors(); if ((descriptors->number_of_descriptors() > 0) && (descriptors->GetKey(0) == GetHeap()->hidden_symbol())) { if (descriptors->GetType(0) == FIELD) { this->FastPropertyAtPut(descriptors->GetFieldIndex(0), dictionary); return this; } else { ASSERT(descriptors->GetType(0) == NULL_DESCRIPTOR || descriptors->GetType(0) == MAP_TRANSITION); } } } MaybeObject* store_result = SetPropertyPostInterceptor(GetHeap()->hidden_symbol(), dictionary, DONT_ENUM, kNonStrictMode); if (store_result->IsFailure()) return store_result; return this; } MaybeObject* JSObject::DeletePropertyPostInterceptor(String* name, DeleteMode mode) { // Check local property, ignore interceptor. LookupResult result(GetIsolate()); LocalLookupRealNamedProperty(name, &result); if (!result.IsProperty()) return GetHeap()->true_value(); // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } return DeleteNormalizedProperty(name, mode); } MaybeObject* JSObject::DeletePropertyWithInterceptor(String* name) { Isolate* isolate = GetIsolate(); HandleScope scope(isolate); Handle interceptor(GetNamedInterceptor()); Handle name_handle(name); Handle this_handle(this); if (!interceptor->deleter()->IsUndefined()) { v8::NamedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); LOG(isolate, ApiNamedPropertyAccess("interceptor-named-delete", *this_handle, name)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(v8::Utils::ToLocal(name_handle), info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } } MaybeObject* raw_result = this_handle->DeletePropertyPostInterceptor(*name_handle, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } MaybeObject* JSObject::DeleteElementWithInterceptor(uint32_t index) { Isolate* isolate = GetIsolate(); Heap* heap = isolate->heap(); // Make sure that the top context does not change when doing // callbacks or interceptor calls. AssertNoContextChange ncc; HandleScope scope(isolate); Handle interceptor(GetIndexedInterceptor()); if (interceptor->deleter()->IsUndefined()) return heap->false_value(); v8::IndexedPropertyDeleter deleter = v8::ToCData(interceptor->deleter()); Handle this_handle(this); LOG(isolate, ApiIndexedPropertyAccess("interceptor-indexed-delete", this, index)); CustomArguments args(isolate, interceptor->data(), this, this); v8::AccessorInfo info(args.end()); v8::Handle result; { // Leaving JavaScript. VMState state(isolate, EXTERNAL); result = deleter(index, info); } RETURN_IF_SCHEDULED_EXCEPTION(isolate); if (!result.IsEmpty()) { ASSERT(result->IsBoolean()); return *v8::Utils::OpenHandle(*result); } MaybeObject* raw_result = this_handle->GetElementsAccessor()->Delete( *this_handle, index, NORMAL_DELETION); RETURN_IF_SCHEDULED_EXCEPTION(isolate); return raw_result; } Handle JSObject::DeleteElement(Handle obj, uint32_t index) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteElement(index, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { Isolate* isolate = GetIsolate(); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayIndexedAccess(this, index, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteElement(index, mode); } if (HasIndexedInterceptor()) { // Skip interceptor if forcing deletion. if (mode != FORCE_DELETION) { return DeleteElementWithInterceptor(index); } mode = JSReceiver::FORCE_DELETION; } return GetElementsAccessor()->Delete(this, index, mode); } Handle JSObject::DeleteProperty(Handle obj, Handle prop) { CALL_HEAP_FUNCTION(obj->GetIsolate(), obj->DeleteProperty(*prop, JSObject::NORMAL_DELETION), Object); } MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) { Isolate* isolate = GetIsolate(); // ECMA-262, 3rd, 8.6.2.5 ASSERT(name->IsString()); // Check access rights if needed. if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, name, v8::ACCESS_DELETE)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_DELETE); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return isolate->heap()->false_value(); ASSERT(proto->IsJSGlobalObject()); return JSGlobalObject::cast(proto)->DeleteProperty(name, mode); } uint32_t index = 0; if (name->AsArrayIndex(&index)) { return DeleteElement(index, mode); } else { LookupResult result(isolate); LocalLookup(name, &result); if (!result.IsProperty()) return isolate->heap()->true_value(); // Ignore attributes if forcing a deletion. if (result.IsDontDelete() && mode != FORCE_DELETION) { if (mode == STRICT_DELETION) { // Deleting a non-configurable property in strict mode. HandleScope scope(isolate); Handle args[2] = { Handle(name), Handle(this) }; return isolate->Throw(*isolate->factory()->NewTypeError( "strict_delete_property", HandleVector(args, 2))); } return isolate->heap()->false_value(); } // Check for interceptor. if (result.type() == INTERCEPTOR) { // Skip interceptor if forcing a deletion. if (mode == FORCE_DELETION) { return DeletePropertyPostInterceptor(name, mode); } return DeletePropertyWithInterceptor(name); } // Normalize object if needed. Object* obj; { MaybeObject* maybe_obj = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0); if (!maybe_obj->ToObject(&obj)) return maybe_obj; } // Make sure the properties are normalized before removing the entry. return DeleteNormalizedProperty(name, mode); } } MaybeObject* JSReceiver::DeleteElement(uint32_t index, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeleteElementWithHandler(index, mode); } return JSObject::cast(this)->DeleteElement(index, mode); } MaybeObject* JSReceiver::DeleteProperty(String* name, DeleteMode mode) { if (IsJSProxy()) { return JSProxy::cast(this)->DeletePropertyWithHandler(name, mode); } return JSObject::cast(this)->DeleteProperty(name, mode); } bool JSObject::ReferencesObjectFromElements(FixedArray* elements, ElementsKind kind, Object* object) { ASSERT(kind == FAST_ELEMENTS || kind == DICTIONARY_ELEMENTS); if (kind == FAST_ELEMENTS) { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : elements->length(); for (int i = 0; i < length; ++i) { Object* element = elements->get(i); if (!element->IsTheHole() && element == object) return true; } } else { Object* key = SeededNumberDictionary::cast(elements)->SlowReverseLookup(object); if (!key->IsUndefined()) return true; } return false; } // Check whether this object references another object. bool JSObject::ReferencesObject(Object* obj) { Map* map_of_this = map(); Heap* heap = GetHeap(); AssertNoAllocation no_alloc; // Is the object the constructor for this object? if (map_of_this->constructor() == obj) { return true; } // Is the object the prototype for this object? if (map_of_this->prototype() == obj) { return true; } // Check if the object is among the named properties. Object* key = SlowReverseLookup(obj); if (!key->IsUndefined()) { return true; } // Check if the object is among the indexed properties. ElementsKind kind = GetElementsKind(); switch (kind) { case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: case FAST_DOUBLE_ELEMENTS: // Raw pixels and external arrays do not reference other // objects. break; case FAST_SMI_ONLY_ELEMENTS: break; case FAST_ELEMENTS: case DICTIONARY_ELEMENTS: { FixedArray* elements = FixedArray::cast(this->elements()); if (ReferencesObjectFromElements(elements, kind, obj)) return true; break; } case NON_STRICT_ARGUMENTS_ELEMENTS: { FixedArray* parameter_map = FixedArray::cast(elements()); // Check the mapped parameters. int length = parameter_map->length(); for (int i = 2; i < length; ++i) { Object* value = parameter_map->get(i); if (!value->IsTheHole() && value == obj) return true; } // Check the arguments. FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); kind = arguments->IsDictionary() ? DICTIONARY_ELEMENTS : FAST_ELEMENTS; if (ReferencesObjectFromElements(arguments, kind, obj)) return true; break; } } // For functions check the context. if (IsJSFunction()) { // Get the constructor function for arguments array. JSObject* arguments_boilerplate = heap->isolate()->context()->global_context()-> arguments_boilerplate(); JSFunction* arguments_function = JSFunction::cast(arguments_boilerplate->map()->constructor()); // Get the context and don't check if it is the global context. JSFunction* f = JSFunction::cast(this); Context* context = f->context(); if (context->IsGlobalContext()) { return false; } // Check the non-special context slots. for (int i = Context::MIN_CONTEXT_SLOTS; i < context->length(); i++) { // Only check JS objects. if (context->get(i)->IsJSObject()) { JSObject* ctxobj = JSObject::cast(context->get(i)); // If it is an arguments array check the content. if (ctxobj->map()->constructor() == arguments_function) { if (ctxobj->ReferencesObject(obj)) { return true; } } else if (ctxobj == obj) { return true; } } } // Check the context extension (if any) if it can have references. if (context->has_extension() && !context->IsCatchContext()) { return JSObject::cast(context->extension())->ReferencesObject(obj); } } // No references to object. return false; } Handle JSObject::PreventExtensions(Handle object) { CALL_HEAP_FUNCTION(object->GetIsolate(), object->PreventExtensions(), Object); } MaybeObject* JSObject::PreventExtensions() { Isolate* isolate = GetIsolate(); if (IsAccessCheckNeeded() && !isolate->MayNamedAccess(this, isolate->heap()->undefined_value(), v8::ACCESS_KEYS)) { isolate->ReportFailedAccessCheck(this, v8::ACCESS_KEYS); return isolate->heap()->false_value(); } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return this; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->PreventExtensions(); } // It's not possible to seal objects with external array elements if (HasExternalArrayElements()) { HandleScope scope(isolate); Handle object(this); Handle error = isolate->factory()->NewTypeError( "cant_prevent_ext_external_array_elements", HandleVector(&object, 1)); return isolate->Throw(*error); } // If there are fast elements we normalize. SeededNumberDictionary* dictionary = NULL; { MaybeObject* maybe = NormalizeElements(); if (!maybe->To(&dictionary)) return maybe; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Make sure that we never go back to fast case. dictionary->set_requires_slow_elements(); // Do a map transition, other objects with this map may still // be extensible. Map* new_map; { MaybeObject* maybe = map()->CopyDropTransitions(); if (!maybe->To(&new_map)) return maybe; } new_map->set_is_extensible(false); set_map(new_map); ASSERT(!map()->is_extensible()); return new_map; } // Tests for the fast common case for property enumeration: // - This object and all prototypes has an enum cache (which means that // it is no proxy, has no interceptors and needs no access checks). // - This object has no elements. // - No prototype has enumerable properties/elements. bool JSReceiver::IsSimpleEnum() { Heap* heap = GetHeap(); for (Object* o = this; o != heap->null_value(); o = JSObject::cast(o)->GetPrototype()) { if (!o->IsJSObject()) return false; JSObject* curr = JSObject::cast(o); if (!curr->map()->instance_descriptors()->HasEnumCache()) return false; ASSERT(!curr->HasNamedInterceptor()); ASSERT(!curr->HasIndexedInterceptor()); ASSERT(!curr->IsAccessCheckNeeded()); if (curr->NumberOfEnumElements() > 0) return false; if (curr != this) { FixedArray* curr_fixed_array = FixedArray::cast(curr->map()->instance_descriptors()->GetEnumCache()); if (curr_fixed_array->length() > 0) return false; } } return true; } int Map::NumberOfDescribedProperties(PropertyAttributes filter) { int result = 0; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { PropertyDetails details(descs->GetDetails(i)); if (descs->IsProperty(i) && (details.attributes() & filter) == 0) { result++; } } return result; } int Map::PropertyIndexFor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && !descs->IsNullDescriptor(i)) { return descs->GetFieldIndex(i); } } return -1; } int Map::NextFreePropertyIndex() { int max_index = -1; DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (descs->GetType(i) == FIELD) { int current_index = descs->GetFieldIndex(i); if (current_index > max_index) max_index = current_index; } } return max_index + 1; } AccessorDescriptor* Map::FindAccessor(String* name) { DescriptorArray* descs = instance_descriptors(); for (int i = 0; i < descs->number_of_descriptors(); i++) { if (name->Equals(descs->GetKey(i)) && descs->GetType(i) == CALLBACKS) { return descs->GetCallbacks(i); } } return NULL; } void JSReceiver::LocalLookup(String* name, LookupResult* result) { ASSERT(name->IsString()); Heap* heap = GetHeap(); if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); if (proto->IsNull()) return result->NotFound(); ASSERT(proto->IsJSGlobalObject()); return JSReceiver::cast(proto)->LocalLookup(name, result); } if (IsJSProxy()) { result->HandlerResult(JSProxy::cast(this)); return; } // Do not use inline caching if the object is a non-global object // that requires access checks. if (IsAccessCheckNeeded()) { result->DisallowCaching(); } JSObject* js_object = JSObject::cast(this); // Check __proto__ before interceptor. if (name->Equals(heap->Proto_symbol()) && !IsJSContextExtensionObject()) { result->ConstantResult(js_object); return; } // Check for lookup interceptor except when bootstrapping. if (js_object->HasNamedInterceptor() && !heap->isolate()->bootstrapper()->IsActive()) { result->InterceptorResult(js_object); return; } js_object->LocalLookupRealNamedProperty(name, result); } void JSReceiver::Lookup(String* name, LookupResult* result) { // Ecma-262 3rd 8.6.2.4 Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value(); current = JSObject::cast(current)->GetPrototype()) { JSReceiver::cast(current)->LocalLookup(name, result); if (result->IsProperty()) return; } result->NotFound(); } // Search object and it's prototype chain for callback properties. void JSObject::LookupCallback(String* name, LookupResult* result) { Heap* heap = GetHeap(); for (Object* current = this; current != heap->null_value() && current->IsJSObject(); current = JSObject::cast(current)->GetPrototype()) { JSObject::cast(current)->LocalLookupRealNamedProperty(name, result); if (result->IsFound() && result->type() == CALLBACKS) return; } result->NotFound(); } // Try to update an accessor in an elements dictionary. Return true if the // update succeeded, and false otherwise. static bool UpdateGetterSetterInDictionary( SeededNumberDictionary* dictionary, uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { int entry = dictionary->FindEntry(index); if (entry != SeededNumberDictionary::kNotFound) { Object* result = dictionary->ValueAt(entry); PropertyDetails details = dictionary->DetailsAt(entry); if (details.type() == CALLBACKS && result->IsAccessorPair()) { ASSERT(!details.IsDontDelete()); if (details.attributes() != attributes) { dictionary->DetailsAtPut(entry, PropertyDetails(attributes, CALLBACKS, index)); } AccessorPair::cast(result)->SetComponents(getter, setter); return true; } } return false; } MaybeObject* JSObject::DefineElementAccessor(uint32_t index, Object* getter, Object* setter, PropertyAttributes attributes) { switch (GetElementsKind()) { case FAST_SMI_ONLY_ELEMENTS: case FAST_ELEMENTS: case FAST_DOUBLE_ELEMENTS: break; case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: case EXTERNAL_UNSIGNED_SHORT_ELEMENTS: case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: case EXTERNAL_DOUBLE_ELEMENTS: // Ignore getters and setters on pixel and external array elements. return GetHeap()->undefined_value(); case DICTIONARY_ELEMENTS: if (UpdateGetterSetterInDictionary(element_dictionary(), index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } break; case NON_STRICT_ARGUMENTS_ELEMENTS: { // Ascertain whether we have read-only properties or an existing // getter/setter pair in an arguments elements dictionary backing // store. FixedArray* parameter_map = FixedArray::cast(elements()); uint32_t length = parameter_map->length(); Object* probe = index < (length - 2) ? parameter_map->get(index + 2) : NULL; if (probe == NULL || probe->IsTheHole()) { FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); if (arguments->IsDictionary()) { SeededNumberDictionary* dictionary = SeededNumberDictionary::cast(arguments); if (UpdateGetterSetterInDictionary(dictionary, index, getter, setter, attributes)) { return GetHeap()->undefined_value(); } } } break; } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetElementCallback(index, accessors, attributes); } MaybeObject* JSObject::DefinePropertyAccessor(String* name, Object* getter, Object* setter, PropertyAttributes attributes) { // Lookup the name. LookupResult result(GetHeap()->isolate()); LocalLookupRealNamedProperty(name, &result); if (result.IsFound()) { if (result.type() == CALLBACKS) { ASSERT(!result.IsDontDelete()); Object* obj = result.GetCallbackObject(); // Need to preserve old getters/setters. if (obj->IsAccessorPair()) { AccessorPair* copy; { MaybeObject* maybe_copy = AccessorPair::cast(obj)->CopyWithoutTransitions(); if (!maybe_copy->To(©)) return maybe_copy; } copy->SetComponents(getter, setter); // Use set to update attributes. return SetPropertyCallback(name, copy, attributes); } } } AccessorPair* accessors; { MaybeObject* maybe_accessors = GetHeap()->AllocateAccessorPair(); if (!maybe_accessors->To(&accessors)) return maybe_accessors; } accessors->SetComponents(getter, setter); return SetPropertyCallback(name, accessors, attributes); } bool JSObject::CanSetCallback(String* name) { ASSERT(!IsAccessCheckNeeded() || GetIsolate()->MayNamedAccess(this, name, v8::ACCESS_SET)); // Check if there is an API defined callback object which prohibits // callback overwriting in this object or it's prototype chain. // This mechanism is needed for instance in a browser setting, where // certain accessors such as window.location should not be allowed // to be overwritten because allowing overwriting could potentially // cause security problems. LookupResult callback_result(GetIsolate()); LookupCallback(name, &callback_result); if (callback_result.IsProperty()) { Object* obj = callback_result.GetCallbackObject(); if (obj->IsAccessorInfo() && AccessorInfo::cast(obj)->prohibits_overwriting()) { return false; } } return true; } MaybeObject* JSObject::SetElementCallback(uint32_t index, Object* structure, PropertyAttributes attributes) { PropertyDetails details = PropertyDetails(attributes, CALLBACKS); // Normalize elements to make this operation simple. SeededNumberDictionary* dictionary; { MaybeObject* maybe_dictionary = NormalizeElements(); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } ASSERT(HasDictionaryElements() || HasDictionaryArgumentsElements()); // Update the dictionary with the new CALLBACKS property. { MaybeObject* maybe_dictionary = dictionary->Set(index, structure, details); if (!maybe_dictionary->To(&dictionary)) return maybe_dictionary; } dictionary->set_requires_slow_elements(); // Update the dictionary backing store on the object. if (elements()->map() == GetHeap()->non_strict_arguments_elements_map()) { // Also delete any parameter alias. // // TODO(kmillikin): when deleting the last parameter alias we could // switch to a direct backing store without the parameter map. This // would allow GC of the context. FixedArray* parameter_map = FixedArray::cast(elements()); if (index < static_cast