// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <stdlib.h> #include <utility> #include "test/cctest/test-api.h" #include "src/v8.h" #include "src/compilation-cache.h" #include "src/execution.h" #include "src/factory.h" #include "src/global-handles.h" #include "src/ic/stub-cache.h" #include "src/objects.h" using namespace v8::internal; // // Helper functions. // namespace { Handle<String> MakeString(const char* str) { Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); return factory->InternalizeUtf8String(str); } Handle<String> MakeName(const char* str, int suffix) { EmbeddedVector<char, 128> buffer; SNPrintF(buffer, "%s%d", str, suffix); return MakeString(buffer.start()); } template <typename T, typename M> bool EQUALS(Handle<T> left, Handle<M> right) { if (*left == *right) return true; return JSObject::Equals(Handle<Object>::cast(left), Handle<Object>::cast(right)) .FromJust(); } template <typename T, typename M> bool EQUALS(Handle<T> left, M right) { return EQUALS(left, handle(right)); } template <typename T, typename M> bool EQUALS(T left, Handle<M> right) { return EQUALS(handle(left), right); } } // namespace // // Tests // TEST(JSObjectAddingProperties) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); Handle<Object> value(Smi::FromInt(42), isolate); Handle<JSObject> object = factory->NewJSObject(function); Handle<Map> previous_map(object->map()); CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK(EQUALS(object->elements(), empty_fixed_array)); // for the default constructor function no in-object properties are reserved // hence adding a single property will initialize the property-array Handle<String> name = MakeName("property", 0); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); CHECK_NE(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK_LE(1, object->properties()->length()); CHECK(EQUALS(object->elements(), empty_fixed_array)); } TEST(JSObjectInObjectAddingProperties) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); int nof_inobject_properties = 10; // force in object properties by changing the expected_nof_properties function->shared()->set_expected_nof_properties(nof_inobject_properties); Handle<Object> value(Smi::FromInt(42), isolate); Handle<JSObject> object = factory->NewJSObject(function); Handle<Map> previous_map(object->map()); CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK(EQUALS(object->elements(), empty_fixed_array)); // we have reserved space for in-object properties, hence adding up to // |nof_inobject_properties| will not create a property store for (int i = 0; i < nof_inobject_properties; i++) { Handle<String> name = MakeName("property", i); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); } CHECK_NE(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK(EQUALS(object->elements(), empty_fixed_array)); // adding one more property will not fit in the in-object properties, thus // creating a property store int index = nof_inobject_properties + 1; Handle<String> name = MakeName("property", index); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); CHECK_NE(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); // there must be at least 1 element in the properies store CHECK_LE(1, object->properties()->length()); CHECK(EQUALS(object->elements(), empty_fixed_array)); } TEST(JSObjectAddingElements) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<String> name; Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); Handle<JSFunction> function = factory->NewFunction(factory->empty_string()); Handle<Object> value(Smi::FromInt(42), isolate); Handle<JSObject> object = factory->NewJSObject(function); Handle<Map> previous_map(object->map()); CHECK_EQ(previous_map->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK(EQUALS(object->elements(), empty_fixed_array)); // Adding an indexed element initializes the elements array name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); // no change in elements_kind => no map transition CHECK_EQ(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK_LE(1, object->elements()->length()); // Adding more consecutive elements without a change in the backing store int non_dict_backing_store_limit = 100; for (int i = 1; i < non_dict_backing_store_limit; i++) { name = MakeName("", i); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); } // no change in elements_kind => no map transition CHECK_EQ(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK_LE(non_dict_backing_store_limit, object->elements()->length()); // Adding an element at an very large index causes a change to // DICTIONARY_ELEMENTS name = MakeString("100000000"); JSObject::DefinePropertyOrElementIgnoreAttributes(object, name, value, NONE) .Check(); // change in elements_kind => map transition CHECK_NE(object->map(), *previous_map); CHECK_EQ(object->map()->elements_kind(), DICTIONARY_ELEMENTS); CHECK(EQUALS(object->properties(), empty_fixed_array)); CHECK_LE(non_dict_backing_store_limit, object->elements()->length()); } TEST(JSArrayAddingProperties) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); Handle<Object> value(Smi::FromInt(42), isolate); Handle<JSArray> array = factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); Handle<Map> previous_map(array->map()); CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); CHECK(EQUALS(array->properties(), empty_fixed_array)); CHECK(EQUALS(array->elements(), empty_fixed_array)); CHECK_EQ(Smi::cast(array->length())->value(), 0); // for the default constructor function no in-object properties are reserved // hence adding a single property will initialize the property-array Handle<String> name = MakeName("property", 0); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) .Check(); // No change in elements_kind but added property => new map CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); CHECK_LE(1, array->properties()->length()); CHECK(EQUALS(array->elements(), empty_fixed_array)); CHECK_EQ(Smi::cast(array->length())->value(), 0); } TEST(JSArrayAddingElements) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<String> name; Handle<FixedArray> empty_fixed_array(factory->empty_fixed_array()); Handle<Object> value(Smi::FromInt(42), isolate); Handle<JSArray> array = factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); Handle<Map> previous_map(array->map()); CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); CHECK(EQUALS(array->properties(), empty_fixed_array)); CHECK(EQUALS(array->elements(), empty_fixed_array)); CHECK_EQ(Smi::cast(array->length())->value(), 0); // Adding an indexed element initializes the elements array name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) .Check(); // no change in elements_kind => no map transition CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); CHECK(EQUALS(array->properties(), empty_fixed_array)); CHECK_LE(1, array->elements()->length()); CHECK_EQ(1, Smi::cast(array->length())->value()); // Adding more consecutive elements without a change in the backing store int non_dict_backing_store_limit = 100; for (int i = 1; i < non_dict_backing_store_limit; i++) { name = MakeName("", i); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) .Check(); } // no change in elements_kind => no map transition CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); CHECK(EQUALS(array->properties(), empty_fixed_array)); CHECK_LE(non_dict_backing_store_limit, array->elements()->length()); CHECK_EQ(non_dict_backing_store_limit, Smi::cast(array->length())->value()); // Adding an element at an very large index causes a change to // DICTIONARY_ELEMENTS int index = 100000000; name = MakeName("", index); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value, NONE) .Check(); // change in elements_kind => map transition CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), DICTIONARY_ELEMENTS); CHECK(EQUALS(array->properties(), empty_fixed_array)); CHECK_LE(non_dict_backing_store_limit, array->elements()->length()); CHECK_LE(array->elements()->length(), index); CHECK_EQ(index + 1, Smi::cast(array->length())->value()); } TEST(JSArrayAddingElementsGeneralizingiFastSmiElements) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<String> name; Handle<Object> value_smi(Smi::FromInt(42), isolate); Handle<Object> value_string(MakeString("value")); Handle<Object> value_double = factory->NewNumber(3.1415); Handle<JSArray> array = factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); Handle<Map> previous_map(array->map()); CHECK_EQ(previous_map->elements_kind(), FAST_SMI_ELEMENTS); CHECK_EQ(Smi::cast(array->length())->value(), 0); // `array[0] = smi_value` doesn't change the elements_kind name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); // no change in elements_kind => no map transition CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_SMI_ELEMENTS); CHECK_EQ(1, Smi::cast(array->length())->value()); // `delete array[0]` does not alter length, but changes the elments_kind name = MakeString("0"); CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false)); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_SMI_ELEMENTS); CHECK_EQ(1, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // add a couple of elements again name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); name = MakeString("1"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_SMI_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); // Adding a string to the array changes from FAST_HOLEY_SMI to FAST_HOLEY name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, NONE) .Check(); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // We don't transition back to FAST_SMI even if we remove the string name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); // Adding a double doesn't change the map either name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); } TEST(JSArrayAddingElementsGeneralizingFastElements) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<String> name; Handle<Object> value_smi(Smi::FromInt(42), isolate); Handle<Object> value_string(MakeString("value")); Handle<JSArray> array = factory->NewJSArray(ElementsKind::FAST_ELEMENTS, 0, 0); Handle<Map> previous_map(array->map()); CHECK_EQ(previous_map->elements_kind(), FAST_ELEMENTS); CHECK_EQ(Smi::cast(array->length())->value(), 0); // `array[0] = smi_value` doesn't change the elements_kind name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); // no change in elements_kind => no map transition CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_ELEMENTS); CHECK_EQ(1, Smi::cast(array->length())->value()); // `delete array[0]` does not alter length, but changes the elments_kind name = MakeString("0"); CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false)); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK_EQ(1, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // add a couple of elements, elements_kind stays HOLEY name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, NONE) .Check(); name = MakeString("1"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); } TEST(JSArrayAddingElementsGeneralizingiFastDoubleElements) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); Factory* factory = isolate->factory(); v8::HandleScope scope(CcTest::isolate()); Handle<String> name; Handle<Object> value_smi(Smi::FromInt(42), isolate); Handle<Object> value_string(MakeString("value")); Handle<Object> value_double = factory->NewNumber(3.1415); Handle<JSArray> array = factory->NewJSArray(ElementsKind::FAST_SMI_ELEMENTS, 0, 0); Handle<Map> previous_map(array->map()); // `array[0] = value_double` changes |elements_kind| to FAST_DOUBLE_ELEMENTS name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, NONE) .Check(); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_DOUBLE_ELEMENTS); CHECK_EQ(1, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // `array[1] = value_smi` doesn't alter the |elements_kind| name = MakeString("1"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_smi, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_DOUBLE_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); // `delete array[0]` does not alter length, but changes the elments_kind name = MakeString("0"); CHECK(JSReceiver::DeletePropertyOrElement(array, name).FromMaybe(false)); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_DOUBLE_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // filling the hole `array[0] = value_smi` again doesn't transition back name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_DOUBLE_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); // Adding a string to the array changes to elements_kind FAST_ELEMENTS name = MakeString("1"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_string, NONE) .Check(); CHECK_NE(array->map(), *previous_map); CHECK_EQ(array->map()->elements_kind(), FAST_HOLEY_ELEMENTS); CHECK_EQ(2, Smi::cast(array->length())->value()); previous_map = handle(array->map()); // Adding a double doesn't change the map name = MakeString("0"); JSObject::DefinePropertyOrElementIgnoreAttributes(array, name, value_double, NONE) .Check(); CHECK_EQ(array->map(), *previous_map); }