7d9f79168b8ec8969d83c03ef12ec040a817b13f
[platform/upstream/v8.git] / src / lookup.cc
1 // Copyright 2014 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "src/v8.h"
6
7 #include "src/bootstrapper.h"
8 #include "src/deoptimizer.h"
9 #include "src/lookup.h"
10 #include "src/lookup-inl.h"
11
12 namespace v8 {
13 namespace internal {
14
15
16 void LookupIterator::Next() {
17   DCHECK_NE(JSPROXY, state_);
18   DCHECK_NE(TRANSITION, state_);
19   DisallowHeapAllocation no_gc;
20   has_property_ = false;
21
22   JSReceiver* holder = *holder_;
23   Map* map = *holder_map_;
24
25   // Perform lookup on current holder.
26   state_ = LookupInHolder(map, holder);
27   if (IsFound()) return;
28
29   // Continue lookup if lookup on current holder failed.
30   do {
31     JSReceiver* maybe_holder = NextHolder(map);
32     if (maybe_holder == nullptr) {
33       if (interceptor_state_ == InterceptorState::kSkipNonMasking) {
34         RestartLookupForNonMaskingInterceptors();
35         return;
36       }
37       break;
38     }
39     holder = maybe_holder;
40     map = holder->map();
41     state_ = LookupInHolder(map, holder);
42   } while (!IsFound());
43
44   if (holder != *holder_) {
45     holder_ = handle(holder, isolate_);
46     holder_map_ = handle(map, isolate_);
47   }
48 }
49
50
51 void LookupIterator::RestartLookupForNonMaskingInterceptors() {
52   interceptor_state_ = InterceptorState::kProcessNonMasking;
53   state_ = NOT_FOUND;
54   property_details_ = PropertyDetails::Empty();
55   number_ = DescriptorArray::kNotFound;
56   holder_ = initial_holder_;
57   holder_map_ = handle(holder_->map(), isolate_);
58   Next();
59 }
60
61
62 // static
63 Handle<JSReceiver> LookupIterator::GetRoot(Isolate* isolate,
64                                            Handle<Object> receiver,
65                                            uint32_t index) {
66   if (receiver->IsJSReceiver()) return Handle<JSReceiver>::cast(receiver);
67   // Strings are the only objects with properties (only elements) directly on
68   // the wrapper. Hence we can skip generating the wrapper for all other cases.
69   if (index != kMaxUInt32 && receiver->IsString() &&
70       index < static_cast<uint32_t>(String::cast(*receiver)->length())) {
71     // TODO(verwaest): Speed this up. Perhaps use a cached wrapper on the native
72     // context, ensuring that we don't leak it into JS?
73     Handle<JSFunction> constructor = isolate->string_function();
74     Handle<JSObject> result = isolate->factory()->NewJSObject(constructor);
75     Handle<JSValue>::cast(result)->set_value(*receiver);
76     return result;
77   }
78   auto root = handle(receiver->GetRootMap(isolate)->prototype(), isolate);
79   if (root->IsNull()) {
80     unsigned int magic = 0xbbbbbbbb;
81     isolate->PushStackTraceAndDie(magic, *receiver, NULL, magic);
82   }
83   return Handle<JSReceiver>::cast(root);
84 }
85
86
87 Handle<Map> LookupIterator::GetReceiverMap() const {
88   if (receiver_->IsNumber()) return factory()->heap_number_map();
89   return handle(Handle<HeapObject>::cast(receiver_)->map(), isolate_);
90 }
91
92
93 Handle<JSObject> LookupIterator::GetStoreTarget() const {
94   if (receiver_->IsJSGlobalProxy()) {
95     PrototypeIterator iter(isolate(), receiver_);
96     if (iter.IsAtEnd()) return Handle<JSGlobalProxy>::cast(receiver_);
97     return Handle<JSGlobalObject>::cast(PrototypeIterator::GetCurrent(iter));
98   }
99   return Handle<JSObject>::cast(receiver_);
100 }
101
102
103 bool LookupIterator::HasAccess() const {
104   DCHECK_EQ(ACCESS_CHECK, state_);
105   return isolate_->MayAccess(GetHolder<JSObject>());
106 }
107
108
109 void LookupIterator::ReloadPropertyInformation() {
110   state_ = BEFORE_PROPERTY;
111   interceptor_state_ = InterceptorState::kUninitialized;
112   state_ = LookupInHolder(*holder_map_, *holder_);
113   DCHECK(IsFound() || holder_map_->is_dictionary_map());
114 }
115
116
117 void LookupIterator::ReloadHolderMap() {
118   DCHECK_EQ(DATA, state_);
119   DCHECK(IsElement());
120   DCHECK(JSObject::cast(*holder_)->HasExternalArrayElements() ||
121          JSObject::cast(*holder_)->HasFixedTypedArrayElements());
122   if (*holder_map_ != holder_->map()) {
123     holder_map_ = handle(holder_->map(), isolate_);
124   }
125 }
126
127
128 void LookupIterator::PrepareForDataProperty(Handle<Object> value) {
129   DCHECK(state_ == DATA || state_ == ACCESSOR);
130   DCHECK(HolderIsReceiverOrHiddenPrototype());
131
132   Handle<JSObject> holder = GetHolder<JSObject>();
133
134   if (IsElement()) {
135     ElementsKind kind = holder_map_->elements_kind();
136     ElementsKind to = value->OptimalElementsKind();
137     if (IsHoleyElementsKind(kind)) to = GetHoleyElementsKind(to);
138     to = IsMoreGeneralElementsKindTransition(kind, to) ? to : kind;
139     JSObject::TransitionElementsKind(holder, to);
140     holder_map_ = handle(holder->map(), isolate_);
141
142     // Copy the backing store if it is copy-on-write.
143     if (IsFastSmiOrObjectElementsKind(to)) {
144       JSObject::EnsureWritableFastElements(holder);
145     }
146
147   } else {
148     if (holder_map_->is_dictionary_map()) return;
149     holder_map_ =
150         Map::PrepareForDataProperty(holder_map_, descriptor_number(), value);
151   }
152
153   JSObject::MigrateToMap(holder, holder_map_);
154   ReloadPropertyInformation();
155 }
156
157
158 void LookupIterator::ReconfigureDataProperty(Handle<Object> value,
159                                              PropertyAttributes attributes) {
160   DCHECK(state_ == DATA || state_ == ACCESSOR);
161   DCHECK(HolderIsReceiverOrHiddenPrototype());
162   Handle<JSObject> holder = GetHolder<JSObject>();
163   if (IsElement()) {
164     DCHECK(!holder->HasExternalArrayElements());
165     DCHECK(!holder->HasFixedTypedArrayElements());
166     DCHECK(attributes != NONE || !holder->HasFastElements());
167     Handle<FixedArrayBase> elements(holder->elements());
168     holder->GetElementsAccessor()->Reconfigure(holder, elements, number_, value,
169                                                attributes);
170   } else if (holder_map_->is_dictionary_map()) {
171     PropertyDetails details(attributes, v8::internal::DATA, 0,
172                             PropertyCellType::kMutable);
173     JSObject::SetNormalizedProperty(holder, name(), value, details);
174   } else {
175     holder_map_ = Map::ReconfigureExistingProperty(
176         holder_map_, descriptor_number(), i::kData, attributes);
177     holder_map_ =
178         Map::PrepareForDataProperty(holder_map_, descriptor_number(), value);
179     JSObject::MigrateToMap(holder, holder_map_);
180   }
181
182   ReloadPropertyInformation();
183 }
184
185
186 void LookupIterator::PrepareTransitionToDataProperty(
187     Handle<Object> value, PropertyAttributes attributes,
188     Object::StoreFromKeyed store_mode) {
189   if (state_ == TRANSITION) return;
190   DCHECK(state_ != LookupIterator::ACCESSOR ||
191          (GetAccessors()->IsAccessorInfo() &&
192           AccessorInfo::cast(*GetAccessors())->is_special_data_property()));
193   DCHECK_NE(INTEGER_INDEXED_EXOTIC, state_);
194   DCHECK(state_ == NOT_FOUND || !HolderIsReceiverOrHiddenPrototype());
195   // Can only be called when the receiver is a JSObject. JSProxy has to be
196   // handled via a trap. Adding properties to primitive values is not
197   // observable.
198   Handle<JSObject> receiver = GetStoreTarget();
199
200   if (!isolate()->IsInternallyUsedPropertyName(name()) &&
201       !receiver->map()->is_extensible()) {
202     return;
203   }
204
205   auto transition = Map::TransitionToDataProperty(
206       handle(receiver->map(), isolate_), name_, value, attributes, store_mode);
207   state_ = TRANSITION;
208   transition_ = transition;
209
210   if (receiver->IsGlobalObject()) {
211     // Install a property cell.
212     InternalizeName();
213     auto cell = GlobalObject::EnsurePropertyCell(
214         Handle<GlobalObject>::cast(receiver), name());
215     DCHECK(cell->value()->IsTheHole());
216     transition_ = cell;
217   } else if (!transition->is_dictionary_map()) {
218     property_details_ = transition->GetLastDescriptorDetails();
219     has_property_ = true;
220   }
221 }
222
223
224 void LookupIterator::ApplyTransitionToDataProperty() {
225   DCHECK_EQ(TRANSITION, state_);
226
227   Handle<JSObject> receiver = GetStoreTarget();
228   if (receiver->IsGlobalObject()) return;
229   holder_ = receiver;
230   holder_map_ = transition_map();
231   JSObject::MigrateToMap(receiver, holder_map_);
232   ReloadPropertyInformation();
233 }
234
235
236 void LookupIterator::Delete() {
237   Handle<JSObject> holder = Handle<JSObject>::cast(holder_);
238   if (IsElement()) {
239     ElementsAccessor* accessor = holder->GetElementsAccessor();
240     accessor->Delete(holder, number_);
241   } else {
242     PropertyNormalizationMode mode = holder->map()->is_prototype_map()
243                                          ? KEEP_INOBJECT_PROPERTIES
244                                          : CLEAR_INOBJECT_PROPERTIES;
245
246     if (holder->HasFastProperties()) {
247       JSObject::NormalizeProperties(holder, mode, 0, "DeletingProperty");
248       holder_map_ = handle(holder->map(), isolate_);
249       ReloadPropertyInformation();
250     }
251     // TODO(verwaest): Get rid of the name_ argument.
252     JSObject::DeleteNormalizedProperty(holder, name_, number_);
253     JSObject::ReoptimizeIfPrototype(holder);
254   }
255 }
256
257
258 void LookupIterator::TransitionToAccessorProperty(
259     AccessorComponent component, Handle<Object> accessor,
260     PropertyAttributes attributes) {
261   DCHECK(!accessor->IsNull());
262   // Can only be called when the receiver is a JSObject. JSProxy has to be
263   // handled via a trap. Adding properties to primitive values is not
264   // observable.
265   Handle<JSObject> receiver = GetStoreTarget();
266
267   if (!IsElement() && !receiver->map()->is_dictionary_map()) {
268     holder_ = receiver;
269     holder_map_ = Map::TransitionToAccessorProperty(
270         handle(receiver->map(), isolate_), name_, component, accessor,
271         attributes);
272     JSObject::MigrateToMap(receiver, holder_map_);
273
274     ReloadPropertyInformation();
275
276     if (!holder_map_->is_dictionary_map()) return;
277   }
278
279   Handle<AccessorPair> pair;
280   if (state() == ACCESSOR && GetAccessors()->IsAccessorPair()) {
281     pair = Handle<AccessorPair>::cast(GetAccessors());
282     // If the component and attributes are identical, nothing has to be done.
283     if (pair->get(component) == *accessor) {
284       if (property_details().attributes() == attributes) return;
285     } else {
286       pair = AccessorPair::Copy(pair);
287       pair->set(component, *accessor);
288     }
289   } else {
290     pair = factory()->NewAccessorPair();
291     pair->set(component, *accessor);
292   }
293
294   TransitionToAccessorPair(pair, attributes);
295 }
296
297
298 void LookupIterator::TransitionToAccessorPair(Handle<Object> pair,
299                                               PropertyAttributes attributes) {
300   Handle<JSObject> receiver = GetStoreTarget();
301   holder_ = receiver;
302
303   PropertyDetails details(attributes, ACCESSOR_CONSTANT, 0,
304                           PropertyCellType::kMutable);
305
306   if (IsElement()) {
307     // TODO(verwaest): Move code into the element accessor.
308     Handle<SeededNumberDictionary> dictionary =
309         JSObject::NormalizeElements(receiver);
310
311     dictionary = SeededNumberDictionary::Set(dictionary, index_, pair, details);
312     receiver->RequireSlowElements(*dictionary);
313
314     if (receiver->HasSlowArgumentsElements()) {
315       FixedArray* parameter_map = FixedArray::cast(receiver->elements());
316       uint32_t length = parameter_map->length() - 2;
317       if (number_ < length) {
318         parameter_map->set(number_ + 2, heap()->the_hole_value());
319       }
320       FixedArray::cast(receiver->elements())->set(1, *dictionary);
321     } else {
322       receiver->set_elements(*dictionary);
323     }
324   } else {
325     PropertyNormalizationMode mode = receiver->map()->is_prototype_map()
326                                          ? KEEP_INOBJECT_PROPERTIES
327                                          : CLEAR_INOBJECT_PROPERTIES;
328     // Normalize object to make this operation simple.
329     JSObject::NormalizeProperties(receiver, mode, 0,
330                                   "TransitionToAccessorPair");
331
332     JSObject::SetNormalizedProperty(receiver, name_, pair, details);
333     JSObject::ReoptimizeIfPrototype(receiver);
334   }
335
336   holder_map_ = handle(receiver->map(), isolate_);
337   ReloadPropertyInformation();
338 }
339
340
341 bool LookupIterator::HolderIsReceiverOrHiddenPrototype() const {
342   DCHECK(has_property_ || state_ == INTERCEPTOR || state_ == JSPROXY);
343   return InternalHolderIsReceiverOrHiddenPrototype();
344 }
345
346 bool LookupIterator::InternalHolderIsReceiverOrHiddenPrototype() const {
347   // Optimization that only works if configuration_ is not mutable.
348   if (!check_prototype_chain()) return true;
349   DisallowHeapAllocation no_gc;
350   if (!receiver_->IsJSReceiver()) return false;
351   Object* current = *receiver_;
352   JSReceiver* holder = *holder_;
353   // JSProxy do not occur as hidden prototypes.
354   if (current->IsJSProxy()) {
355     return JSReceiver::cast(current) == holder;
356   }
357   PrototypeIterator iter(isolate(), current,
358                          PrototypeIterator::START_AT_RECEIVER);
359   do {
360     if (JSReceiver::cast(iter.GetCurrent()) == holder) return true;
361     DCHECK(!current->IsJSProxy());
362     iter.Advance();
363   } while (!iter.IsAtEnd(PrototypeIterator::END_AT_NON_HIDDEN));
364   return false;
365 }
366
367
368 Handle<Object> LookupIterator::FetchValue() const {
369   Object* result = NULL;
370   Handle<JSObject> holder = GetHolder<JSObject>();
371   if (IsElement()) {
372     // TODO(verwaest): Optimize.
373     if (holder->IsStringObjectWithCharacterAt(index_)) {
374       Handle<JSValue> js_value = Handle<JSValue>::cast(holder);
375       Handle<String> string(String::cast(js_value->value()));
376       return factory()->LookupSingleCharacterStringFromCode(
377           String::Flatten(string)->Get(index_));
378     }
379
380     ElementsAccessor* accessor = holder->GetElementsAccessor();
381     return accessor->Get(handle(holder->elements()), number_);
382   } else if (holder_map_->IsGlobalObjectMap()) {
383     result = holder->global_dictionary()->ValueAt(number_);
384     DCHECK(result->IsPropertyCell());
385     result = PropertyCell::cast(result)->value();
386   } else if (holder_map_->is_dictionary_map()) {
387     result = holder->property_dictionary()->ValueAt(number_);
388   } else if (property_details_.type() == v8::internal::DATA) {
389     FieldIndex field_index = FieldIndex::ForDescriptor(*holder_map_, number_);
390     return JSObject::FastPropertyAt(holder, property_details_.representation(),
391                                     field_index);
392   } else {
393     result = holder_map_->instance_descriptors()->GetValue(number_);
394   }
395   return handle(result, isolate_);
396 }
397
398
399 int LookupIterator::GetAccessorIndex() const {
400   DCHECK(has_property_);
401   DCHECK(!holder_map_->is_dictionary_map());
402   DCHECK_EQ(v8::internal::ACCESSOR_CONSTANT, property_details_.type());
403   return descriptor_number();
404 }
405
406
407 int LookupIterator::GetConstantIndex() const {
408   DCHECK(has_property_);
409   DCHECK(!holder_map_->is_dictionary_map());
410   DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
411   DCHECK(!IsElement());
412   return descriptor_number();
413 }
414
415
416 FieldIndex LookupIterator::GetFieldIndex() const {
417   DCHECK(has_property_);
418   DCHECK(!holder_map_->is_dictionary_map());
419   DCHECK_EQ(v8::internal::DATA, property_details_.type());
420   DCHECK(!IsElement());
421   int index =
422       holder_map_->instance_descriptors()->GetFieldIndex(descriptor_number());
423   bool is_double = representation().IsDouble();
424   return FieldIndex::ForPropertyIndex(*holder_map_, index, is_double);
425 }
426
427
428 Handle<HeapType> LookupIterator::GetFieldType() const {
429   DCHECK(has_property_);
430   DCHECK(!holder_map_->is_dictionary_map());
431   DCHECK_EQ(v8::internal::DATA, property_details_.type());
432   return handle(
433       holder_map_->instance_descriptors()->GetFieldType(descriptor_number()),
434       isolate_);
435 }
436
437
438 Handle<PropertyCell> LookupIterator::GetPropertyCell() const {
439   DCHECK(!IsElement());
440   Handle<JSObject> holder = GetHolder<JSObject>();
441   Handle<GlobalObject> global = Handle<GlobalObject>::cast(holder);
442   Object* value = global->global_dictionary()->ValueAt(dictionary_entry());
443   DCHECK(value->IsPropertyCell());
444   return handle(PropertyCell::cast(value));
445 }
446
447
448 Handle<Object> LookupIterator::GetAccessors() const {
449   DCHECK_EQ(ACCESSOR, state_);
450   return FetchValue();
451 }
452
453
454 Handle<Object> LookupIterator::GetDataValue() const {
455   DCHECK_EQ(DATA, state_);
456   Handle<Object> value = FetchValue();
457   return value;
458 }
459
460
461 void LookupIterator::WriteDataValue(Handle<Object> value) {
462   DCHECK_EQ(DATA, state_);
463   Handle<JSObject> holder = GetHolder<JSObject>();
464   if (IsElement()) {
465     ElementsAccessor* accessor = holder->GetElementsAccessor();
466     accessor->Set(holder->elements(), number_, *value);
467   } else if (holder->IsGlobalObject()) {
468     Handle<GlobalDictionary> property_dictionary =
469         handle(holder->global_dictionary());
470     PropertyCell::UpdateCell(property_dictionary, dictionary_entry(), value,
471                              property_details_);
472   } else if (holder_map_->is_dictionary_map()) {
473     NameDictionary* property_dictionary = holder->property_dictionary();
474     property_dictionary->ValueAtPut(dictionary_entry(), *value);
475   } else if (property_details_.type() == v8::internal::DATA) {
476     holder->WriteToField(descriptor_number(), *value);
477   } else {
478     DCHECK_EQ(v8::internal::DATA_CONSTANT, property_details_.type());
479   }
480 }
481
482
483 bool LookupIterator::IsIntegerIndexedExotic(JSReceiver* holder) {
484   DCHECK(exotic_index_state_ != ExoticIndexState::kNotExotic);
485   // Currently typed arrays are the only such objects.
486   if (!holder->IsJSTypedArray()) return false;
487   if (exotic_index_state_ == ExoticIndexState::kExotic) return true;
488   if (!InternalHolderIsReceiverOrHiddenPrototype()) {
489     exotic_index_state_ = ExoticIndexState::kNotExotic;
490     return false;
491   }
492   DCHECK(exotic_index_state_ == ExoticIndexState::kUninitialized);
493   bool result = false;
494   // Compute and cache result.
495   if (IsElement()) {
496     result = index_ >= JSTypedArray::cast(holder)->length_value();
497   } else if (name()->IsString()) {
498     Handle<String> name_string = Handle<String>::cast(name());
499     if (name_string->length() != 0) {
500       result = IsSpecialIndex(isolate_->unicode_cache(), *name_string);
501     }
502   }
503   exotic_index_state_ =
504       result ? ExoticIndexState::kExotic : ExoticIndexState::kNotExotic;
505   return result;
506 }
507
508
509 void LookupIterator::InternalizeName() {
510   if (name_->IsUniqueName()) return;
511   name_ = factory()->InternalizeString(Handle<String>::cast(name_));
512 }
513
514
515 bool LookupIterator::HasInterceptor(Map* map) const {
516   if (IsElement()) return map->has_indexed_interceptor();
517   return map->has_named_interceptor();
518 }
519
520
521 Handle<InterceptorInfo> LookupIterator::GetInterceptor() const {
522   DCHECK_EQ(INTERCEPTOR, state_);
523   return handle(GetInterceptor(JSObject::cast(*holder_)), isolate_);
524 }
525
526
527 InterceptorInfo* LookupIterator::GetInterceptor(JSObject* holder) const {
528   if (IsElement()) return holder->GetIndexedInterceptor();
529   return holder->GetNamedInterceptor();
530 }
531
532
533 bool LookupIterator::SkipInterceptor(JSObject* holder) {
534   auto info = GetInterceptor(holder);
535   // TODO(dcarney): check for symbol/can_intercept_symbols here as well.
536   if (info->non_masking()) {
537     switch (interceptor_state_) {
538       case InterceptorState::kUninitialized:
539         interceptor_state_ = InterceptorState::kSkipNonMasking;
540       // Fall through.
541       case InterceptorState::kSkipNonMasking:
542         return true;
543       case InterceptorState::kProcessNonMasking:
544         return false;
545     }
546   }
547   return interceptor_state_ == InterceptorState::kProcessNonMasking;
548 }
549 }  // namespace internal
550 }  // namespace v8