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(&current_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