1// Copyright 2018 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/debug/debug-property-iterator.h" 6 7#include "src/api/api-inl.h" 8#include "src/base/flags.h" 9#include "src/objects/js-array-buffer-inl.h" 10#include "src/objects/keys.h" 11#include "src/objects/property-descriptor.h" 12#include "src/objects/property-details.h" 13 14namespace v8 { 15namespace internal { 16 17std::unique_ptr<DebugPropertyIterator> DebugPropertyIterator::Create( 18 Isolate* isolate, Handle<JSReceiver> receiver, bool skip_indices) { 19 // Can't use std::make_unique as Ctor is private. 20 auto iterator = std::unique_ptr<DebugPropertyIterator>( 21 new DebugPropertyIterator(isolate, receiver, skip_indices)); 22 23 if (receiver->IsJSProxy()) { 24 iterator->AdvanceToPrototype(); 25 } 26 27 if (!iterator->FillKeysForCurrentPrototypeAndStage()) return nullptr; 28 if (iterator->should_move_to_next_stage() && !iterator->AdvanceInternal()) { 29 return nullptr; 30 } 31 32 return iterator; 33} 34 35DebugPropertyIterator::DebugPropertyIterator(Isolate* isolate, 36 Handle<JSReceiver> receiver, 37 bool skip_indices) 38 : isolate_(isolate), 39 prototype_iterator_(isolate, receiver, kStartAtReceiver, 40 PrototypeIterator::END_AT_NULL), 41 skip_indices_(skip_indices), 42 current_key_index_(0), 43 current_keys_(isolate_->factory()->empty_fixed_array()), 44 current_keys_length_(0) {} 45 46bool DebugPropertyIterator::Done() const { return is_done_; } 47 48void DebugPropertyIterator::AdvanceToPrototype() { 49 stage_ = kExoticIndices; 50 is_own_ = false; 51 if (!prototype_iterator_.HasAccess()) is_done_ = true; 52 prototype_iterator_.AdvanceIgnoringProxies(); 53 if (prototype_iterator_.IsAtEnd()) is_done_ = true; 54} 55 56bool DebugPropertyIterator::AdvanceInternal() { 57 ++current_key_index_; 58 calculated_native_accessor_flags_ = false; 59 while (should_move_to_next_stage()) { 60 switch (stage_) { 61 case kExoticIndices: 62 stage_ = kEnumerableStrings; 63 break; 64 case kEnumerableStrings: 65 stage_ = kAllProperties; 66 break; 67 case kAllProperties: 68 AdvanceToPrototype(); 69 break; 70 } 71 if (!FillKeysForCurrentPrototypeAndStage()) return false; 72 } 73 return true; 74} 75 76bool DebugPropertyIterator::is_native_accessor() { 77 CalculateNativeAccessorFlags(); 78 return native_accessor_flags_; 79} 80 81bool DebugPropertyIterator::has_native_getter() { 82 CalculateNativeAccessorFlags(); 83 return native_accessor_flags_ & 84 static_cast<int>(debug::NativeAccessorType::HasGetter); 85} 86 87bool DebugPropertyIterator::has_native_setter() { 88 CalculateNativeAccessorFlags(); 89 return native_accessor_flags_ & 90 static_cast<int>(debug::NativeAccessorType::HasSetter); 91} 92 93Handle<Name> DebugPropertyIterator::raw_name() const { 94 DCHECK(!Done()); 95 if (stage_ == kExoticIndices) { 96 return isolate_->factory()->SizeToString(current_key_index_); 97 } else { 98 return Handle<Name>::cast(FixedArray::get( 99 *current_keys_, static_cast<int>(current_key_index_), isolate_)); 100 } 101} 102 103v8::Local<v8::Name> DebugPropertyIterator::name() const { 104 return Utils::ToLocal(raw_name()); 105} 106 107v8::Maybe<v8::PropertyAttribute> DebugPropertyIterator::attributes() { 108 Handle<JSReceiver> receiver = 109 PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); 110 auto result = JSReceiver::GetPropertyAttributes(receiver, raw_name()); 111 if (result.IsNothing()) return Nothing<v8::PropertyAttribute>(); 112 // This should almost never happen, however we have seen cases where we do 113 // trigger this check. In these rare events, it typically is a 114 // misconfiguration by an embedder (such as Blink) in how the embedder 115 // processes properities. 116 // 117 // In the case of crbug.com/1262066 we discovered that Blink was returning 118 // a list of properties to contain in an object, after which V8 queries each 119 // property individually. But, Blink incorrectly claimed that the property 120 // in question did *not* exist. As such, V8 is instructed to process a 121 // property, requests the embedder for more information and then suddenly the 122 // embedder claims it doesn't exist. In these cases, we hit this DCHECK. 123 // 124 // If you are running into this problem, check your embedder implementation 125 // and verify that the data from both sides matches. If there is a mismatch, 126 // V8 will crash. 127 128#if DEBUG 129 base::ScopedVector<char> property_message(128); 130 base::ScopedVector<char> name_buffer(100); 131 raw_name()->NameShortPrint(name_buffer); 132 v8::base::SNPrintF(property_message, "Invalid result for property \"%s\"\n", 133 name_buffer.begin()); 134 DCHECK_WITH_MSG(result.FromJust() != ABSENT, property_message.begin()); 135#endif 136 return Just(static_cast<v8::PropertyAttribute>(result.FromJust())); 137} 138 139v8::Maybe<v8::debug::PropertyDescriptor> DebugPropertyIterator::descriptor() { 140 Handle<JSReceiver> receiver = 141 PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); 142 143 PropertyDescriptor descriptor; 144 Maybe<bool> did_get_descriptor = JSReceiver::GetOwnPropertyDescriptor( 145 isolate_, receiver, raw_name(), &descriptor); 146 if (did_get_descriptor.IsNothing()) { 147 return Nothing<v8::debug::PropertyDescriptor>(); 148 } 149 DCHECK(did_get_descriptor.FromJust()); 150 return Just(v8::debug::PropertyDescriptor{ 151 descriptor.enumerable(), descriptor.has_enumerable(), 152 descriptor.configurable(), descriptor.has_configurable(), 153 descriptor.writable(), descriptor.has_writable(), 154 descriptor.has_value() ? Utils::ToLocal(descriptor.value()) 155 : v8::Local<v8::Value>(), 156 descriptor.has_get() ? Utils::ToLocal(descriptor.get()) 157 : v8::Local<v8::Value>(), 158 descriptor.has_set() ? Utils::ToLocal(descriptor.set()) 159 : v8::Local<v8::Value>(), 160 }); 161} 162 163bool DebugPropertyIterator::is_own() { return is_own_; } 164 165bool DebugPropertyIterator::is_array_index() { 166 if (stage_ == kExoticIndices) return true; 167 PropertyKey key(isolate_, raw_name()); 168 return key.is_element(); 169} 170 171bool DebugPropertyIterator::FillKeysForCurrentPrototypeAndStage() { 172 current_key_index_ = 0; 173 current_keys_ = isolate_->factory()->empty_fixed_array(); 174 current_keys_length_ = 0; 175 if (is_done_) return true; 176 Handle<JSReceiver> receiver = 177 PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); 178 if (stage_ == kExoticIndices) { 179 if (skip_indices_ || !receiver->IsJSTypedArray()) return true; 180 Handle<JSTypedArray> typed_array = Handle<JSTypedArray>::cast(receiver); 181 current_keys_length_ = 182 typed_array->WasDetached() ? 0 : typed_array->length(); 183 return true; 184 } 185 PropertyFilter filter = 186 stage_ == kEnumerableStrings ? ENUMERABLE_STRINGS : ALL_PROPERTIES; 187 if (KeyAccumulator::GetKeys(receiver, KeyCollectionMode::kOwnOnly, filter, 188 GetKeysConversion::kConvertToString, false, 189 skip_indices_ || receiver->IsJSTypedArray()) 190 .ToHandle(¤t_keys_)) { 191 current_keys_length_ = current_keys_->length(); 192 return true; 193 } 194 return false; 195} 196 197bool DebugPropertyIterator::should_move_to_next_stage() const { 198 return !is_done_ && current_key_index_ >= current_keys_length_; 199} 200 201namespace { 202base::Flags<debug::NativeAccessorType, int> GetNativeAccessorDescriptorInternal( 203 Handle<JSReceiver> object, Handle<Name> name) { 204 Isolate* isolate = object->GetIsolate(); 205 PropertyKey key(isolate, name); 206 if (key.is_element()) return debug::NativeAccessorType::None; 207 LookupIterator it(isolate, object, key, LookupIterator::OWN); 208 if (!it.IsFound()) return debug::NativeAccessorType::None; 209 if (it.state() != LookupIterator::ACCESSOR) { 210 return debug::NativeAccessorType::None; 211 } 212 Handle<Object> structure = it.GetAccessors(); 213 if (!structure->IsAccessorInfo()) return debug::NativeAccessorType::None; 214 base::Flags<debug::NativeAccessorType, int> result; 215#define IS_BUILTIN_ACCESSOR(_, name, ...) \ 216 if (*structure == *isolate->factory()->name##_accessor()) \ 217 return debug::NativeAccessorType::None; 218 ACCESSOR_INFO_LIST_GENERATOR(IS_BUILTIN_ACCESSOR, /* not used */) 219#undef IS_BUILTIN_ACCESSOR 220 Handle<AccessorInfo> accessor_info = Handle<AccessorInfo>::cast(structure); 221 if (accessor_info->getter() != Object()) { 222 result |= debug::NativeAccessorType::HasGetter; 223 } 224 if (accessor_info->setter() != Object()) { 225 result |= debug::NativeAccessorType::HasSetter; 226 } 227 return result; 228} 229} // anonymous namespace 230 231void DebugPropertyIterator::CalculateNativeAccessorFlags() { 232 if (calculated_native_accessor_flags_) return; 233 if (stage_ == kExoticIndices) { 234 native_accessor_flags_ = 0; 235 } else { 236 Handle<JSReceiver> receiver = 237 PrototypeIterator::GetCurrent<JSReceiver>(prototype_iterator_); 238 native_accessor_flags_ = 239 GetNativeAccessorDescriptorInternal(receiver, raw_name()); 240 } 241 calculated_native_accessor_flags_ = true; 242} 243} // namespace internal 244} // namespace v8 245