Line data Source code
1 : // Copyright 2015 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 <stdlib.h>
6 : #include <utility>
7 :
8 : #include "test/cctest/test-api.h"
9 :
10 : #include "src/v8.h"
11 :
12 : #include "src/compilation-cache.h"
13 : #include "src/execution.h"
14 : #include "src/factory.h"
15 : #include "src/global-handles.h"
16 : #include "src/ic/stub-cache.h"
17 : #include "src/objects-inl.h"
18 :
19 : namespace v8 {
20 : namespace internal {
21 : namespace test_elements_kind {
22 :
23 : //
24 : // Helper functions.
25 : //
26 :
27 : namespace {
28 :
29 : Handle<String> MakeString(const char* str) {
30 : Isolate* isolate = CcTest::i_isolate();
31 : Factory* factory = isolate->factory();
32 1410 : return factory->InternalizeUtf8String(str);
33 : }
34 :
35 :
36 1272 : Handle<String> MakeName(const char* str, int suffix) {
37 : EmbeddedVector<char, 128> buffer;
38 1272 : SNPrintF(buffer, "%s%d", str, suffix);
39 2544 : return MakeString(buffer.start());
40 : }
41 :
42 :
43 : template <typename T, typename M>
44 126 : bool EQUALS(Handle<T> left, Handle<M> right) {
45 126 : if (*left == *right) return true;
46 : return JSObject::Equals(Handle<Object>::cast(left),
47 : Handle<Object>::cast(right))
48 0 : .FromJust();
49 : }
50 :
51 :
52 : template <typename T, typename M>
53 : bool EQUALS(Handle<T> left, M right) {
54 : return EQUALS(left, handle(right));
55 : }
56 :
57 :
58 : template <typename T, typename M>
59 126 : bool EQUALS(T left, Handle<M> right) {
60 126 : return EQUALS(handle(left, right->GetIsolate()), right);
61 : }
62 :
63 : } // namespace
64 :
65 :
66 : //
67 : // Tests
68 : //
69 :
70 23724 : TEST(JSObjectAddingProperties) {
71 6 : CcTest::InitializeVM();
72 : Isolate* isolate = CcTest::i_isolate();
73 : Factory* factory = isolate->factory();
74 6 : v8::HandleScope scope(CcTest::isolate());
75 :
76 6 : Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
77 6 : Handle<PropertyArray> empty_property_array(factory->empty_property_array());
78 6 : Handle<JSFunction> function = factory->NewFunction(factory->empty_string());
79 : Handle<Object> value(Smi::FromInt(42), isolate);
80 :
81 6 : Handle<JSObject> object = factory->NewJSObject(function);
82 : Handle<Map> previous_map(object->map());
83 6 : CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
84 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
85 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
86 :
87 : // for the default constructor function no in-object properties are reserved
88 : // hence adding a single property will initialize the property-array
89 6 : Handle<String> name = MakeName("property", 0);
90 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
91 12 : .Check();
92 6 : CHECK_NE(object->map(), *previous_map);
93 6 : CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
94 6 : CHECK_LE(1, object->property_array()->length());
95 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
96 6 : }
97 :
98 :
99 23724 : TEST(JSObjectInObjectAddingProperties) {
100 6 : CcTest::InitializeVM();
101 : Isolate* isolate = CcTest::i_isolate();
102 : Factory* factory = isolate->factory();
103 6 : v8::HandleScope scope(CcTest::isolate());
104 :
105 6 : Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
106 6 : Handle<PropertyArray> empty_property_array(factory->empty_property_array());
107 6 : Handle<JSFunction> function = factory->NewFunction(factory->empty_string());
108 : int nof_inobject_properties = 10;
109 : // force in object properties by changing the expected_nof_properties
110 : function->shared()->set_expected_nof_properties(nof_inobject_properties);
111 : Handle<Object> value(Smi::FromInt(42), isolate);
112 :
113 6 : Handle<JSObject> object = factory->NewJSObject(function);
114 : Handle<Map> previous_map(object->map());
115 6 : CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
116 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
117 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
118 :
119 : // we have reserved space for in-object properties, hence adding up to
120 : // |nof_inobject_properties| will not create a property store
121 60 : for (int i = 0; i < nof_inobject_properties; i++) {
122 60 : Handle<String> name = MakeName("property", i);
123 60 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
124 120 : .Check();
125 : }
126 6 : CHECK_NE(object->map(), *previous_map);
127 6 : CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
128 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
129 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
130 :
131 : // adding one more property will not fit in the in-object properties, thus
132 : // creating a property store
133 : int index = nof_inobject_properties + 1;
134 6 : Handle<String> name = MakeName("property", index);
135 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
136 12 : .Check();
137 6 : CHECK_NE(object->map(), *previous_map);
138 6 : CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
139 : // there must be at least 1 element in the properies store
140 6 : CHECK_LE(1, object->property_array()->length());
141 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
142 6 : }
143 :
144 :
145 23724 : TEST(JSObjectAddingElements) {
146 6 : CcTest::InitializeVM();
147 : Isolate* isolate = CcTest::i_isolate();
148 : Factory* factory = isolate->factory();
149 6 : v8::HandleScope scope(CcTest::isolate());
150 :
151 : Handle<String> name;
152 6 : Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
153 6 : Handle<PropertyArray> empty_property_array(factory->empty_property_array());
154 6 : Handle<JSFunction> function = factory->NewFunction(factory->empty_string());
155 : Handle<Object> value(Smi::FromInt(42), isolate);
156 :
157 6 : Handle<JSObject> object = factory->NewJSObject(function);
158 : Handle<Map> previous_map(object->map());
159 6 : CHECK_EQ(HOLEY_ELEMENTS, previous_map->elements_kind());
160 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
161 6 : CHECK(EQUALS(object->elements(), empty_fixed_array));
162 :
163 : // Adding an indexed element initializes the elements array
164 : name = MakeString("0");
165 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
166 12 : .Check();
167 : // no change in elements_kind => no map transition
168 6 : CHECK_EQ(object->map(), *previous_map);
169 6 : CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
170 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
171 6 : CHECK_LE(1, object->elements()->length());
172 :
173 : // Adding more consecutive elements without a change in the backing store
174 : int non_dict_backing_store_limit = 100;
175 594 : for (int i = 1; i < non_dict_backing_store_limit; i++) {
176 594 : name = MakeName("", i);
177 594 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
178 1188 : .Check();
179 : }
180 : // no change in elements_kind => no map transition
181 6 : CHECK_EQ(object->map(), *previous_map);
182 6 : CHECK_EQ(HOLEY_ELEMENTS, object->map()->elements_kind());
183 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
184 6 : CHECK_LE(non_dict_backing_store_limit, object->elements()->length());
185 :
186 : // Adding an element at an very large index causes a change to
187 : // DICTIONARY_ELEMENTS
188 : name = MakeString("100000000");
189 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE)
190 12 : .Check();
191 : // change in elements_kind => map transition
192 6 : CHECK_NE(object->map(), *previous_map);
193 6 : CHECK_EQ(DICTIONARY_ELEMENTS, object->map()->elements_kind());
194 6 : CHECK(EQUALS(object->property_array(), empty_property_array));
195 6 : CHECK_LE(non_dict_backing_store_limit, object->elements()->length());
196 6 : }
197 :
198 :
199 23724 : TEST(JSArrayAddingProperties) {
200 6 : CcTest::InitializeVM();
201 : Isolate* isolate = CcTest::i_isolate();
202 : Factory* factory = isolate->factory();
203 6 : v8::HandleScope scope(CcTest::isolate());
204 :
205 6 : Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
206 6 : Handle<PropertyArray> empty_property_array(factory->empty_property_array());
207 : Handle<Object> value(Smi::FromInt(42), isolate);
208 :
209 : Handle<JSArray> array =
210 6 : factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
211 : Handle<Map> previous_map(array->map());
212 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
213 6 : CHECK(EQUALS(array->property_array(), empty_property_array));
214 6 : CHECK(EQUALS(array->elements(), empty_fixed_array));
215 6 : CHECK_EQ(0, Smi::ToInt(array->length()));
216 :
217 : // for the default constructor function no in-object properties are reserved
218 : // hence adding a single property will initialize the property-array
219 6 : Handle<String> name = MakeName("property", 0);
220 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
221 12 : .Check();
222 : // No change in elements_kind but added property => new map
223 6 : CHECK_NE(array->map(), *previous_map);
224 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
225 6 : CHECK_LE(1, array->property_array()->length());
226 6 : CHECK(EQUALS(array->elements(), empty_fixed_array));
227 6 : CHECK_EQ(0, Smi::ToInt(array->length()));
228 6 : }
229 :
230 :
231 23724 : TEST(JSArrayAddingElements) {
232 6 : CcTest::InitializeVM();
233 : Isolate* isolate = CcTest::i_isolate();
234 : Factory* factory = isolate->factory();
235 6 : v8::HandleScope scope(CcTest::isolate());
236 :
237 : Handle<String> name;
238 6 : Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array());
239 6 : Handle<PropertyArray> empty_property_array(factory->empty_property_array());
240 : Handle<Object> value(Smi::FromInt(42), isolate);
241 :
242 : Handle<JSArray> array =
243 6 : factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
244 : Handle<Map> previous_map(array->map());
245 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
246 6 : CHECK(EQUALS(array->property_array(), empty_property_array));
247 6 : CHECK(EQUALS(array->elements(), empty_fixed_array));
248 6 : CHECK_EQ(0, Smi::ToInt(array->length()));
249 :
250 : // Adding an indexed element initializes the elements array
251 : name = MakeString("0");
252 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
253 12 : .Check();
254 : // no change in elements_kind => no map transition
255 6 : CHECK_EQ(array->map(), *previous_map);
256 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
257 6 : CHECK(EQUALS(array->property_array(), empty_property_array));
258 6 : CHECK_LE(1, array->elements()->length());
259 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
260 :
261 : // Adding more consecutive elements without a change in the backing store
262 : int non_dict_backing_store_limit = 100;
263 594 : for (int i = 1; i < non_dict_backing_store_limit; i++) {
264 594 : name = MakeName("", i);
265 594 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
266 1188 : .Check();
267 : }
268 : // no change in elements_kind => no map transition
269 6 : CHECK_EQ(array->map(), *previous_map);
270 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
271 6 : CHECK(EQUALS(array->property_array(), empty_property_array));
272 6 : CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
273 6 : CHECK_EQ(non_dict_backing_store_limit, Smi::ToInt(array->length()));
274 :
275 : // Adding an element at an very large index causes a change to
276 : // DICTIONARY_ELEMENTS
277 : int index = 100000000;
278 6 : name = MakeName("", index);
279 6 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE)
280 12 : .Check();
281 : // change in elements_kind => map transition
282 6 : CHECK_NE(array->map(), *previous_map);
283 6 : CHECK_EQ(DICTIONARY_ELEMENTS, array->map()->elements_kind());
284 6 : CHECK(EQUALS(array->property_array(), empty_property_array));
285 6 : CHECK_LE(non_dict_backing_store_limit, array->elements()->length());
286 6 : CHECK_LE(array->elements()->length(), index);
287 6 : CHECK_EQ(index + 1, Smi::ToInt(array->length()));
288 6 : }
289 :
290 :
291 23724 : TEST(JSArrayAddingElementsGeneralizingiFastSmiElements) {
292 6 : CcTest::InitializeVM();
293 : Isolate* isolate = CcTest::i_isolate();
294 : Factory* factory = isolate->factory();
295 6 : v8::HandleScope scope(CcTest::isolate());
296 :
297 : Handle<String> name;
298 : Handle<Object> value_smi(Smi::FromInt(42), isolate);
299 : Handle<Object> value_string(MakeString("value"));
300 6 : Handle<Object> value_double = factory->NewNumber(3.1415);
301 :
302 : Handle<JSArray> array =
303 6 : factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
304 : Handle<Map> previous_map(array->map());
305 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, previous_map->elements_kind());
306 6 : CHECK_EQ(0, Smi::ToInt(array->length()));
307 :
308 : // `array[0] = smi_value` doesn't change the elements_kind
309 : name = MakeString("0");
310 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
311 6 : NONE)
312 12 : .Check();
313 : // no change in elements_kind => no map transition
314 6 : CHECK_EQ(array->map(), *previous_map);
315 6 : CHECK_EQ(PACKED_SMI_ELEMENTS, array->map()->elements_kind());
316 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
317 :
318 : // `delete array[0]` does not alter length, but changes the elments_kind
319 : name = MakeString("0");
320 12 : CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
321 6 : CHECK_NE(array->map(), *previous_map);
322 6 : CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
323 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
324 : previous_map = handle(array->map());
325 :
326 : // add a couple of elements again
327 : name = MakeString("0");
328 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
329 6 : NONE)
330 12 : .Check();
331 : name = MakeString("1");
332 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
333 6 : NONE)
334 12 : .Check();
335 6 : CHECK_EQ(array->map(), *previous_map);
336 6 : CHECK_EQ(HOLEY_SMI_ELEMENTS, array->map()->elements_kind());
337 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
338 :
339 : // Adding a string to the array changes from FAST_HOLEY_SMI to FAST_HOLEY
340 : name = MakeString("0");
341 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
342 6 : NONE)
343 12 : .Check();
344 6 : CHECK_NE(array->map(), *previous_map);
345 6 : CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
346 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
347 : previous_map = handle(array->map());
348 :
349 : // We don't transition back to FAST_SMI even if we remove the string
350 : name = MakeString("0");
351 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
352 6 : NONE)
353 12 : .Check();
354 6 : CHECK_EQ(array->map(), *previous_map);
355 :
356 : // Adding a double doesn't change the map either
357 : name = MakeString("0");
358 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
359 6 : NONE)
360 12 : .Check();
361 6 : CHECK_EQ(array->map(), *previous_map);
362 6 : }
363 :
364 :
365 23724 : TEST(JSArrayAddingElementsGeneralizingFastElements) {
366 6 : CcTest::InitializeVM();
367 : Isolate* isolate = CcTest::i_isolate();
368 : Factory* factory = isolate->factory();
369 6 : v8::HandleScope scope(CcTest::isolate());
370 :
371 : Handle<String> name;
372 : Handle<Object> value_smi(Smi::FromInt(42), isolate);
373 : Handle<Object> value_string(MakeString("value"));
374 :
375 : Handle<JSArray> array =
376 6 : factory->NewJSArray(ElementsKind::PACKED_ELEMENTS, 0, 0);
377 : Handle<Map> previous_map(array->map());
378 6 : CHECK_EQ(PACKED_ELEMENTS, previous_map->elements_kind());
379 6 : CHECK_EQ(0, Smi::ToInt(array->length()));
380 :
381 : // `array[0] = smi_value` doesn't change the elements_kind
382 : name = MakeString("0");
383 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
384 6 : NONE)
385 12 : .Check();
386 : // no change in elements_kind => no map transition
387 6 : CHECK_EQ(array->map(), *previous_map);
388 6 : CHECK_EQ(PACKED_ELEMENTS, array->map()->elements_kind());
389 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
390 :
391 : // `delete array[0]` does not alter length, but changes the elments_kind
392 : name = MakeString("0");
393 12 : CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
394 6 : CHECK_NE(array->map(), *previous_map);
395 6 : CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
396 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
397 : previous_map = handle(array->map());
398 :
399 : // add a couple of elements, elements_kind stays HOLEY
400 : name = MakeString("0");
401 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
402 6 : NONE)
403 12 : .Check();
404 : name = MakeString("1");
405 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
406 6 : NONE)
407 12 : .Check();
408 6 : CHECK_EQ(array->map(), *previous_map);
409 6 : CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
410 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
411 6 : }
412 :
413 :
414 23724 : TEST(JSArrayAddingElementsGeneralizingiFastDoubleElements) {
415 6 : CcTest::InitializeVM();
416 : Isolate* isolate = CcTest::i_isolate();
417 : Factory* factory = isolate->factory();
418 6 : v8::HandleScope scope(CcTest::isolate());
419 :
420 : Handle<String> name;
421 : Handle<Object> value_smi(Smi::FromInt(42), isolate);
422 : Handle<Object> value_string(MakeString("value"));
423 6 : Handle<Object> value_double = factory->NewNumber(3.1415);
424 :
425 : Handle<JSArray> array =
426 6 : factory->NewJSArray(ElementsKind::PACKED_SMI_ELEMENTS, 0, 0);
427 : Handle<Map> previous_map(array->map());
428 :
429 : // `array[0] = value_double` changes |elements_kind| to PACKED_DOUBLE_ELEMENTS
430 : name = MakeString("0");
431 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
432 6 : NONE)
433 12 : .Check();
434 6 : CHECK_NE(array->map(), *previous_map);
435 6 : CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
436 6 : CHECK_EQ(1, Smi::ToInt(array->length()));
437 : previous_map = handle(array->map());
438 :
439 : // `array[1] = value_smi` doesn't alter the |elements_kind|
440 : name = MakeString("1");
441 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi,
442 6 : NONE)
443 12 : .Check();
444 6 : CHECK_EQ(array->map(), *previous_map);
445 6 : CHECK_EQ(PACKED_DOUBLE_ELEMENTS, array->map()->elements_kind());
446 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
447 :
448 : // `delete array[0]` does not alter length, but changes the elments_kind
449 : name = MakeString("0");
450 12 : CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false));
451 6 : CHECK_NE(array->map(), *previous_map);
452 6 : CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
453 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
454 : previous_map = handle(array->map());
455 :
456 : // filling the hole `array[0] = value_smi` again doesn't transition back
457 : name = MakeString("0");
458 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
459 6 : NONE)
460 12 : .Check();
461 6 : CHECK_EQ(array->map(), *previous_map);
462 6 : CHECK_EQ(HOLEY_DOUBLE_ELEMENTS, array->map()->elements_kind());
463 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
464 :
465 : // Adding a string to the array changes to elements_kind PACKED_ELEMENTS
466 : name = MakeString("1");
467 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string,
468 6 : NONE)
469 12 : .Check();
470 6 : CHECK_NE(array->map(), *previous_map);
471 6 : CHECK_EQ(HOLEY_ELEMENTS, array->map()->elements_kind());
472 6 : CHECK_EQ(2, Smi::ToInt(array->length()));
473 : previous_map = handle(array->map());
474 :
475 : // Adding a double doesn't change the map
476 : name = MakeString("0");
477 : JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double,
478 6 : NONE)
479 12 : .Check();
480 6 : CHECK_EQ(array->map(), *previous_map);
481 6 : }
482 :
483 : } // namespace test_elements_kind
484 : } // namespace internal
485 71154 : } // namespace v8
|