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
14 namespace v8 {
15 namespace internal {
16
Create(Isolate * isolate,Handle<JSReceiver> receiver,bool skip_indices)17 std::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
DebugPropertyIterator(Isolate * isolate,Handle<JSReceiver> receiver,bool skip_indices)35 DebugPropertyIterator::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
Done() const46 bool DebugPropertyIterator::Done() const { return is_done_; }
47
AdvanceToPrototype()48 void 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
AdvanceInternal()56 bool 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
is_native_accessor()76 bool DebugPropertyIterator::is_native_accessor() {
77 CalculateNativeAccessorFlags();
78 return native_accessor_flags_;
79 }
80
has_native_getter()81 bool DebugPropertyIterator::has_native_getter() {
82 CalculateNativeAccessorFlags();
83 return native_accessor_flags_ &
84 static_cast<int>(debug::NativeAccessorType::HasGetter);
85 }
86
has_native_setter()87 bool DebugPropertyIterator::has_native_setter() {
88 CalculateNativeAccessorFlags();
89 return native_accessor_flags_ &
90 static_cast<int>(debug::NativeAccessorType::HasSetter);
91 }
92
raw_name() const93 Handle<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
name() const103 v8::Local<v8::Name> DebugPropertyIterator::name() const {
104 return Utils::ToLocal(raw_name());
105 }
106
attributes()107 v8::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
descriptor()139 v8::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
is_own()163 bool DebugPropertyIterator::is_own() { return is_own_; }
164
is_array_index()165 bool DebugPropertyIterator::is_array_index() {
166 if (stage_ == kExoticIndices) return true;
167 PropertyKey key(isolate_, raw_name());
168 return key.is_element();
169 }
170
FillKeysForCurrentPrototypeAndStage()171 bool 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
should_move_to_next_stage() const197 bool DebugPropertyIterator::should_move_to_next_stage() const {
198 return !is_done_ && current_key_index_ >= current_keys_length_;
199 }
200
201 namespace {
GetNativeAccessorDescriptorInternal(Handle<JSReceiver> object,Handle<Name> name)202 base::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
CalculateNativeAccessorFlags()231 void 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