// Copyright 2020 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 "src/base/logging.h" #include "src/codegen/code-stub-assembler.h" #include "src/common/globals.h" #include "src/objects/descriptor-array.h" #include "src/objects/property-details.h" #include "src/objects/string-inl.h" #include "src/objects/transitions-inl.h" #include "test/cctest/cctest.h" #include "test/cctest/compiler/code-assembler-tester.h" #include "test/cctest/compiler/function-tester.h" #include "test/cctest/test-transitions.h" namespace v8 { namespace internal { namespace { using Label = compiler::CodeAssemblerLabel; template using TVariable = compiler::TypedCodeAssemblerVariable; Handle NewNameWithHash(Isolate* isolate, const char* str, uint32_t hash, bool is_integer) { uint32_t hash_field = hash << Name::kHashShift; static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); static_assert(Name::kHashNotComputedMask == 1, "This test needs updating"); static_assert(Name::kIsNotIntegerIndexMask == 2, "This test needs updating"); if (!is_integer) { hash_field |= Name::kIsNotIntegerIndexMask; } Handle name = isolate->factory()->NewOneByteInternalizedString( OneByteVector(str), hash_field); name->set_raw_hash_field(hash_field); CHECK(name->IsUniqueName()); return name; } template MaybeHandle Call(Isolate* isolate, Handle function, Args... args) { const int nof_args = sizeof...(Args); Handle call_args[] = {args...}; Handle receiver = isolate->factory()->undefined_value(); return Execution::Call(isolate, function, receiver, nof_args, call_args); } void CheckDescriptorArrayLookups(Isolate* isolate, Handle map, std::vector>& names, Handle csa_lookup) { // Test C++ implementation. { DisallowGarbageCollection no_gc; DescriptorArray descriptors = map->instance_descriptors(kRelaxedLoad); DCHECK(descriptors.IsSortedNoDuplicates()); int nof_descriptors = descriptors.number_of_descriptors(); for (size_t i = 0; i < names.size(); ++i) { Name name = *names[i]; InternalIndex index = descriptors.Search(name, nof_descriptors, false); CHECK(index.is_found()); CHECK_EQ(i, index.as_uint32()); } } // Test CSA implementation. if (!FLAG_jitless) { for (size_t i = 0; i < names.size(); ++i) { Handle name_index = Call(isolate, csa_lookup, map, names[i]).ToHandleChecked(); CHECK(name_index->IsSmi()); CHECK_EQ(DescriptorArray::ToKeyIndex(static_cast(i)), Smi::ToInt(*name_index)); } } } void CheckTransitionArrayLookups(Isolate* isolate, Handle transitions, std::vector>& maps, Handle csa_lookup) { // Test C++ implementation. { DisallowGarbageCollection no_gc; DCHECK(transitions->IsSortedNoDuplicates()); for (size_t i = 0; i < maps.size(); ++i) { Map expected_map = *maps[i]; Name name = expected_map.instance_descriptors(kRelaxedLoad) .GetKey(expected_map.LastAdded()); Map map = transitions->SearchAndGetTargetForTesting(PropertyKind::kData, name, NONE); CHECK(!map.is_null()); CHECK_EQ(expected_map, map); } } // Test CSA implementation. if (!FLAG_jitless) { for (size_t i = 0; i < maps.size(); ++i) { Handle expected_map = maps[i]; Handle name(expected_map->instance_descriptors(kRelaxedLoad) .GetKey(expected_map->LastAdded()), isolate); Handle transition_map = Call(isolate, csa_lookup, transitions, name).ToHandleChecked(); CHECK(transition_map->IsMap()); CHECK_EQ(*expected_map, *transition_map); } } } // Creates function with (Map, Name) arguments. Returns Smi with the index of // the name value of the found descriptor (DescriptorArray::ToKeyIndex()) // or null otherwise. Handle CreateCsaDescriptorArrayLookup(Isolate* isolate) { // We are not allowed to generate code in jitless mode. if (FLAG_jitless) return Handle(); // Preallocate handle for the result in the current handle scope. Handle result_function(JSFunction{}, isolate); const int kNumParams = 2; compiler::CodeAssemblerTester asm_tester( isolate, kNumParams + 1, // +1 to include receiver. CodeKind::FOR_TESTING); { CodeStubAssembler m(asm_tester.state()); auto map = m.Parameter(1); auto unique_name = m.Parameter(2); Label passed(&m), failed(&m); Label if_found(&m), if_not_found(&m); TVariable var_name_index(&m); TNode bit_field3 = m.LoadMapBitField3(map); TNode descriptors = m.LoadMapDescriptors(map); m.DescriptorLookup(unique_name, descriptors, bit_field3, &if_found, &var_name_index, &if_not_found); m.BIND(&if_found); m.Return(m.SmiTag(var_name_index.value())); m.BIND(&if_not_found); m.Return(m.NullConstant()); } { compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams); // Copy function value to a handle created in the outer handle scope. result_function.PatchValue(*ft.function); } return result_function; } // Creates function with (TransitionArray, Name) arguments. Returns transition // map if transition is found or null otherwise. Handle CreateCsaTransitionArrayLookup(Isolate* isolate) { // We are not allowed to generate code in jitless mode. if (FLAG_jitless) return Handle(); // Preallocate handle for the result in the current handle scope. Handle result_function(JSFunction{}, isolate); const int kNumParams = 2; compiler::CodeAssemblerTester asm_tester( isolate, kNumParams + 1, // +1 to include receiver. CodeKind::FOR_TESTING); { CodeStubAssembler m(asm_tester.state()); auto transitions = m.Parameter(1); auto unique_name = m.Parameter(2); Label passed(&m), failed(&m); Label if_found(&m), if_not_found(&m); TVariable var_name_index(&m); m.TransitionLookup(unique_name, transitions, &if_found, &var_name_index, &if_not_found); m.BIND(&if_found); { STATIC_ASSERT(kData == 0); STATIC_ASSERT(NONE == 0); const int kKeyToTargetOffset = (TransitionArray::kEntryTargetIndex - TransitionArray::kEntryKeyIndex) * kTaggedSize; TNode transition_map = m.CAST(m.GetHeapObjectAssumeWeak( m.LoadArrayElement(transitions, WeakFixedArray::kHeaderSize, var_name_index.value(), kKeyToTargetOffset))); m.Return(transition_map); } m.BIND(&if_not_found); m.Return(m.NullConstant()); } { compiler::FunctionTester ft(asm_tester.GenerateCode(), kNumParams); // Copy function value to a handle created in the outer handle scope. result_function.PatchValue(*ft.function); } return result_function; } } // namespace TEST(DescriptorArrayHashCollisionMassive) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast(isolate->GenerateIdentityHash(Name::kHashBitMask)); for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) { // Add pairs of names having the same base hash value but having different // values of is_integer bit. bool first_is_integer = (i & 1) != 0; bool second_is_integer = (i & 2) != 0; names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); } // Create descriptor array with the created names by appending fields to some // map. DescriptorArray marking relies on the fact that it's attached to an // owning map. Handle map = Map::Create(isolate, 0); Handle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { map = Map::CopyWithField(isolate, map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), OMIT_TRANSITION) .ToHandleChecked(); } Handle csa_lookup = CreateCsaDescriptorArrayLookup(isolate); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); // Sort descriptor array and check it again. map->instance_descriptors(kRelaxedLoad).Sort(); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); } TEST(DescriptorArrayHashCollision) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); std::vector> names; uint32_t hash = 0; for (int i = 0; i < kMaxNumberOfDescriptors / 2; ++i) { if (i % 2 == 0) { // Change hash value for every pair of names. hash = static_cast( isolate->GenerateIdentityHash(Name::kHashBitMask)); } // Add pairs of names having the same base hash value but having different // values of is_integer bit. bool first_is_integer = (i & 1) != 0; bool second_is_integer = (i & 2) != 0; names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); } // Create descriptor array with the created names by appending fields to some // map. DescriptorArray marking relies on the fact that it's attached to an // owning map. Handle map = Map::Create(isolate, 0); Handle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { map = Map::CopyWithField(isolate, map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), OMIT_TRANSITION) .ToHandleChecked(); } Handle csa_lookup = CreateCsaDescriptorArrayLookup(isolate); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); // Sort descriptor array and check it again. map->instance_descriptors(kRelaxedLoad).Sort(); CheckDescriptorArrayLookups(isolate, map, names, csa_lookup); } TEST(TransitionArrayHashCollisionMassive) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast(isolate->GenerateIdentityHash(Name::kHashBitMask)); for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) { // Add pairs of names having the same base hash value but having different // values of is_integer bit. bool first_is_integer = (i & 1) != 0; bool second_is_integer = (i & 2) != 0; names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); } // Create transitions for each name. Handle root_map = Map::Create(isolate, 0); std::vector> maps; Handle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { Handle map = Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), INSERT_TRANSITION) .ToHandleChecked(); maps.push_back(map); } Handle csa_lookup = CreateCsaTransitionArrayLookup(isolate); Handle transition_array( TestTransitionsAccessor(isolate, root_map).transitions(), isolate); CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); // Sort transition array and check it again. transition_array->Sort(); CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); } TEST(TransitionArrayHashCollision) { CcTest::InitializeVM(); Isolate* isolate = CcTest::i_isolate(); HandleScope handle_scope(isolate); static_assert(Name::kNofHashBitFields == 2, "This test needs updating"); std::vector> names; // Use the same hash value for all names. uint32_t hash = static_cast(isolate->GenerateIdentityHash(Name::kHashBitMask)); for (int i = 0; i < TransitionsAccessor::kMaxNumberOfTransitions / 2; ++i) { if (i % 2 == 0) { // Change hash value for every pair of names. hash = static_cast( isolate->GenerateIdentityHash(Name::kHashBitMask)); } // Add pairs of names having the same base hash value but having different // values of is_integer bit. bool first_is_integer = (i & 1) != 0; bool second_is_integer = (i & 2) != 0; names.push_back(NewNameWithHash(isolate, "a", hash, first_is_integer)); names.push_back(NewNameWithHash(isolate, "b", hash, second_is_integer)); } // Create transitions for each name. Handle root_map = Map::Create(isolate, 0); std::vector> maps; Handle any_type = FieldType::Any(isolate); for (size_t i = 0; i < names.size(); ++i) { Handle map = Map::CopyWithField(isolate, root_map, names[i], any_type, NONE, PropertyConstness::kMutable, Representation::Tagged(), INSERT_TRANSITION) .ToHandleChecked(); maps.push_back(map); } Handle csa_lookup = CreateCsaTransitionArrayLookup(isolate); Handle transition_array( TestTransitionsAccessor(isolate, root_map).transitions(), isolate); CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); // Sort transition array and check it again. transition_array->Sort(); CheckTransitionArrayLookups(isolate, transition_array, maps, csa_lookup); } } // namespace internal } // namespace v8