1 // Copyright 2013 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/key-accumulator.h" 6 7 #include "src/elements.h" 8 #include "src/factory.h" 9 #include "src/isolate-inl.h" 10 #include "src/objects-inl.h" 11 #include "src/property-descriptor.h" 12 13 14 namespace v8 { 15 namespace internal { 16 17 18 KeyAccumulator::~KeyAccumulator() { 19 for (size_t i = 0; i < elements_.size(); i++) { 20 delete elements_[i]; 21 } 22 } 23 24 25 Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) { 26 if (length_ == 0) { 27 return isolate_->factory()->empty_fixed_array(); 28 } 29 // Make sure we have all the lengths collected. 30 NextPrototype(); 31 32 // Assemble the result array by first adding the element keys and then the 33 // property keys. We use the total number of String + Symbol keys per level in 34 // |level_lengths_| and the available element keys in the corresponding bucket 35 // in |elements_| to deduce the number of keys to take from the 36 // |string_properties_| and |symbol_properties_| set. 37 Handle<FixedArray> result = isolate_->factory()->NewFixedArray(length_); 38 int insertion_index = 0; 39 int string_properties_index = 0; 40 int symbol_properties_index = 0; 41 // String and Symbol lengths always come in pairs: 42 size_t max_level = level_lengths_.size() / 2; 43 for (size_t level = 0; level < max_level; level++) { 44 int num_string_properties = level_lengths_[level * 2]; 45 int num_symbol_properties = level_lengths_[level * 2 + 1]; 46 if (num_string_properties < 0) { 47 // If the |num_string_properties| is negative, the current level contains 48 // properties from a proxy, hence we skip the integer keys in |elements_| 49 // since proxies define the complete ordering. 50 num_string_properties = -num_string_properties; 51 } else if (level < elements_.size()) { 52 // Add the element indices for this prototype level. 53 std::vector<uint32_t>* elements = elements_[level]; 54 int num_elements = static_cast<int>(elements->size()); 55 for (int i = 0; i < num_elements; i++) { 56 Handle<Object> key; 57 if (convert == KEEP_NUMBERS) { 58 key = isolate_->factory()->NewNumberFromUint(elements->at(i)); 59 } else { 60 key = isolate_->factory()->Uint32ToString(elements->at(i)); 61 } 62 result->set(insertion_index, *key); 63 insertion_index++; 64 } 65 } 66 // Add the string property keys for this prototype level. 67 for (int i = 0; i < num_string_properties; i++) { 68 Object* key = string_properties_->KeyAt(string_properties_index); 69 result->set(insertion_index, key); 70 insertion_index++; 71 string_properties_index++; 72 } 73 // Add the symbol property keys for this prototype level. 74 for (int i = 0; i < num_symbol_properties; i++) { 75 Object* key = symbol_properties_->KeyAt(symbol_properties_index); 76 result->set(insertion_index, key); 77 insertion_index++; 78 symbol_properties_index++; 79 } 80 } 81 82 DCHECK_EQ(insertion_index, length_); 83 return result; 84 } 85 86 87 namespace { 88 89 bool AccumulatorHasKey(std::vector<uint32_t>* sub_elements, uint32_t key) { 90 return std::binary_search(sub_elements->begin(), sub_elements->end(), key); 91 } 92 93 } // namespace 94 95 bool KeyAccumulator::AddKey(Object* key, AddKeyConversion convert) { 96 return AddKey(handle(key, isolate_), convert); 97 } 98 99 100 bool KeyAccumulator::AddKey(Handle<Object> key, AddKeyConversion convert) { 101 if (key->IsSymbol()) { 102 if (filter_ & SKIP_SYMBOLS) return false; 103 if (Handle<Symbol>::cast(key)->is_private()) return false; 104 return AddSymbolKey(key); 105 } 106 if (filter_ & SKIP_STRINGS) return false; 107 // Make sure we do not add keys to a proxy-level (see AddKeysFromProxy). 108 DCHECK_LE(0, level_string_length_); 109 // In some cases (e.g. proxies) we might get in String-converted ints which 110 // should be added to the elements list instead of the properties. For 111 // proxies we have to convert as well but also respect the original order. 112 // Therefore we add a converted key to both sides 113 if (convert == CONVERT_TO_ARRAY_INDEX || convert == PROXY_MAGIC) { 114 uint32_t index = 0; 115 int prev_length = length_; 116 int prev_proto = level_string_length_; 117 if ((key->IsString() && Handle<String>::cast(key)->AsArrayIndex(&index)) || 118 key->ToArrayIndex(&index)) { 119 bool key_was_added = AddIntegerKey(index); 120 if (convert == CONVERT_TO_ARRAY_INDEX) return key_was_added; 121 if (convert == PROXY_MAGIC) { 122 // If we had an array index (number) and it wasn't added, the key 123 // already existed before, hence we cannot add it to the properties 124 // keys as it would lead to duplicate entries. 125 if (!key_was_added) { 126 return false; 127 } 128 length_ = prev_length; 129 level_string_length_ = prev_proto; 130 } 131 } 132 } 133 return AddStringKey(key, convert); 134 } 135 136 137 bool KeyAccumulator::AddKey(uint32_t key) { return AddIntegerKey(key); } 138 139 140 bool KeyAccumulator::AddIntegerKey(uint32_t key) { 141 // Make sure we do not add keys to a proxy-level (see AddKeysFromProxy). 142 // We mark proxy-levels with a negative length 143 DCHECK_LE(0, level_string_length_); 144 // Binary search over all but the last level. The last one might not be 145 // sorted yet. 146 for (size_t i = 1; i < elements_.size(); i++) { 147 if (AccumulatorHasKey(elements_[i - 1], key)) return false; 148 } 149 elements_.back()->push_back(key); 150 length_++; 151 return true; 152 } 153 154 155 bool KeyAccumulator::AddStringKey(Handle<Object> key, 156 AddKeyConversion convert) { 157 if (string_properties_.is_null()) { 158 string_properties_ = OrderedHashSet::Allocate(isolate_, 16); 159 } 160 // TODO(cbruni): remove this conversion once we throw the correct TypeError 161 // for non-string/symbol elements returned by proxies 162 if (convert == PROXY_MAGIC && key->IsNumber()) { 163 key = isolate_->factory()->NumberToString(key); 164 } 165 int prev_size = string_properties_->NumberOfElements(); 166 string_properties_ = OrderedHashSet::Add(string_properties_, key); 167 if (prev_size < string_properties_->NumberOfElements()) { 168 length_++; 169 level_string_length_++; 170 return true; 171 } else { 172 return false; 173 } 174 } 175 176 177 bool KeyAccumulator::AddSymbolKey(Handle<Object> key) { 178 if (symbol_properties_.is_null()) { 179 symbol_properties_ = OrderedHashSet::Allocate(isolate_, 16); 180 } 181 int prev_size = symbol_properties_->NumberOfElements(); 182 symbol_properties_ = OrderedHashSet::Add(symbol_properties_, key); 183 if (prev_size < symbol_properties_->NumberOfElements()) { 184 length_++; 185 level_symbol_length_++; 186 return true; 187 } else { 188 return false; 189 } 190 } 191 192 193 void KeyAccumulator::AddKeys(Handle<FixedArray> array, 194 AddKeyConversion convert) { 195 int add_length = array->length(); 196 if (add_length == 0) return; 197 for (int i = 0; i < add_length; i++) { 198 Handle<Object> current(array->get(i), isolate_); 199 AddKey(current, convert); 200 } 201 } 202 203 204 void KeyAccumulator::AddKeys(Handle<JSObject> array_like, 205 AddKeyConversion convert) { 206 DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements()); 207 ElementsAccessor* accessor = array_like->GetElementsAccessor(); 208 accessor->AddElementsToKeyAccumulator(array_like, this, convert); 209 } 210 211 212 void KeyAccumulator::AddKeysFromProxy(Handle<JSObject> array_like) { 213 // Proxies define a complete list of keys with no distinction of 214 // elements and properties, which breaks the normal assumption for the 215 // KeyAccumulator. 216 AddKeys(array_like, PROXY_MAGIC); 217 // Invert the current length to indicate a present proxy, so we can ignore 218 // element keys for this level. Otherwise we would not fully respect the order 219 // given by the proxy. 220 level_string_length_ = -level_string_length_; 221 } 222 223 224 MaybeHandle<FixedArray> FilterProxyKeys(Isolate* isolate, Handle<JSProxy> owner, 225 Handle<FixedArray> keys, 226 PropertyFilter filter) { 227 if (filter == ALL_PROPERTIES) { 228 // Nothing to do. 229 return keys; 230 } 231 int store_position = 0; 232 for (int i = 0; i < keys->length(); ++i) { 233 Handle<Name> key(Name::cast(keys->get(i)), isolate); 234 if (key->FilterKey(filter)) continue; // Skip this key. 235 if (filter & ONLY_ENUMERABLE) { 236 PropertyDescriptor desc; 237 Maybe<bool> found = 238 JSProxy::GetOwnPropertyDescriptor(isolate, owner, key, &desc); 239 MAYBE_RETURN(found, MaybeHandle<FixedArray>()); 240 if (!found.FromJust() || !desc.enumerable()) continue; // Skip this key. 241 } 242 // Keep this key. 243 if (store_position != i) { 244 keys->set(store_position, *key); 245 } 246 store_position++; 247 } 248 if (store_position == 0) return isolate->factory()->empty_fixed_array(); 249 keys->Shrink(store_position); 250 return keys; 251 } 252 253 254 // Returns "nothing" in case of exception, "true" on success. 255 Maybe<bool> KeyAccumulator::AddKeysFromProxy(Handle<JSProxy> proxy, 256 Handle<FixedArray> keys) { 257 ASSIGN_RETURN_ON_EXCEPTION_VALUE( 258 isolate_, keys, FilterProxyKeys(isolate_, proxy, keys, filter_), 259 Nothing<bool>()); 260 // Proxies define a complete list of keys with no distinction of 261 // elements and properties, which breaks the normal assumption for the 262 // KeyAccumulator. 263 AddKeys(keys, PROXY_MAGIC); 264 // Invert the current length to indicate a present proxy, so we can ignore 265 // element keys for this level. Otherwise we would not fully respect the order 266 // given by the proxy. 267 level_string_length_ = -level_string_length_; 268 return Just(true); 269 } 270 271 272 void KeyAccumulator::AddElementKeysFromInterceptor( 273 Handle<JSObject> array_like) { 274 AddKeys(array_like, CONVERT_TO_ARRAY_INDEX); 275 // The interceptor might introduce duplicates for the current level, since 276 // these keys get added after the objects's normal element keys. 277 SortCurrentElementsListRemoveDuplicates(); 278 } 279 280 281 void KeyAccumulator::SortCurrentElementsListRemoveDuplicates() { 282 // Sort and remove duplicates from the current elements level and adjust. 283 // the lengths accordingly. 284 auto last_level = elements_.back(); 285 size_t nof_removed_keys = last_level->size(); 286 std::sort(last_level->begin(), last_level->end()); 287 last_level->erase(std::unique(last_level->begin(), last_level->end()), 288 last_level->end()); 289 // Adjust total length by the number of removed duplicates. 290 nof_removed_keys -= last_level->size(); 291 length_ -= static_cast<int>(nof_removed_keys); 292 } 293 294 295 void KeyAccumulator::SortCurrentElementsList() { 296 if (elements_.empty()) return; 297 auto element_keys = elements_.back(); 298 std::sort(element_keys->begin(), element_keys->end()); 299 } 300 301 302 void KeyAccumulator::NextPrototype() { 303 // Store the protoLength on the first call of this method. 304 if (!elements_.empty()) { 305 level_lengths_.push_back(level_string_length_); 306 level_lengths_.push_back(level_symbol_length_); 307 } 308 elements_.push_back(new std::vector<uint32_t>()); 309 level_string_length_ = 0; 310 level_symbol_length_ = 0; 311 } 312 313 314 } // namespace internal 315 } // namespace v8 316