/* * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #include "precompiled.hpp" #include "memory/resourceArea.hpp" #include "utilities/growableArray.hpp" #include "unittest.hpp" struct WithEmbeddedArray { // Array embedded in another class GrowableArray _a; // Resource allocated data array WithEmbeddedArray(int initial_max) : _a(initial_max) {} // Arena allocated data array WithEmbeddedArray(Arena* arena, int initial_max) : _a(arena, initial_max, 0, 0) {} // CHeap allocated data array WithEmbeddedArray(int initial_max, MemTag mem_tag) : _a(initial_max, mem_tag) { assert(mem_tag != mtNone, "test requirement"); } WithEmbeddedArray(const GrowableArray& other) : _a(other) {} }; // Test fixture to work with TEST_VM_F class GrowableArrayTest : public ::testing::Test { protected: // friend -> private accessors template static bool elements_on_C_heap(const GrowableArray* array) { return array->on_C_heap(); } template static bool elements_on_resource_area(const GrowableArray* array) { return array->on_resource_area(); } template static bool elements_on_arena(const GrowableArray* array) { return array->on_arena(); } template static void test_append(ArrayClass* a) { // Add elements for (int i = 0; i < 10; i++) { a->append(i); } // Check size ASSERT_EQ(a->length(), 10); // Check elements for (int i = 0; i < 10; i++) { EXPECT_EQ(a->at(i), i); } } template static void test_clear(ArrayClass* a) { // Add elements for (int i = 0; i < 10; i++) { a->append(i); } // Check size ASSERT_EQ(a->length(), 10); ASSERT_EQ(a->is_empty(), false); // Clear elements a->clear(); // Check size ASSERT_EQ(a->length(), 0); ASSERT_EQ(a->is_empty(), true); // Add element a->append(11); // Check size ASSERT_EQ(a->length(), 1); ASSERT_EQ(a->is_empty(), false); // Clear elements a->clear(); // Check size ASSERT_EQ(a->length(), 0); ASSERT_EQ(a->is_empty(), true); } template static void test_iterator(ArrayClass* a) { // Add elements for (int i = 0; i < 10; i++) { a->append(i); } // Iterate int counter = 0; for (GrowableArrayIterator i = a->begin(); i != a->end(); ++i) { ASSERT_EQ(*i, counter++); } // Check count ASSERT_EQ(counter, 10); } template static void test_capacity(ArrayClass* a) { ASSERT_EQ(a->length(), 0); a->reserve(50); ASSERT_EQ(a->length(), 0); ASSERT_EQ(a->capacity(), 50); for (int i = 0; i < 50; ++i) { a->append(i); } ASSERT_EQ(a->length(), 50); ASSERT_EQ(a->capacity(), 50); a->append(50); ASSERT_EQ(a->length(), 51); int capacity = a->capacity(); ASSERT_GE(capacity, 51); for (int i = 0; i < 30; ++i) { a->pop(); } ASSERT_EQ(a->length(), 21); ASSERT_EQ(a->capacity(), capacity); a->shrink_to_fit(); ASSERT_EQ(a->length(), 21); ASSERT_EQ(a->capacity(), 21); a->reserve(50); ASSERT_EQ(a->length(), 21); ASSERT_EQ(a->capacity(), 50); a->clear(); ASSERT_EQ(a->length(), 0); ASSERT_EQ(a->capacity(), 50); a->shrink_to_fit(); ASSERT_EQ(a->length(), 0); ASSERT_EQ(a->capacity(), 0); } template static void test_copy1(ArrayClass* a) { ASSERT_EQ(a->length(), 1); ASSERT_EQ(a->at(0), 1); // Only allowed to copy to stack and embedded ResourceObjs // Copy to stack { GrowableArray c(*a); ASSERT_EQ(c.length(), 1); ASSERT_EQ(c.at(0), 1); } // Copy to embedded { WithEmbeddedArray c(*a); ASSERT_EQ(c._a.length(), 1); ASSERT_EQ(c._a.at(0), 1); } } template static void test_assignment1(ArrayClass* a) { ASSERT_EQ(a->length(), 1); ASSERT_EQ(a->at(0), 1); // Only allowed to assign to stack and embedded ResourceObjs // Copy to embedded/resource { ResourceMark rm; GrowableArray c(1); c = *a; ASSERT_EQ(c.length(), 1); ASSERT_EQ(c.at(0), 1); } // Copy to embedded/arena { Arena arena(mtTest); GrowableArray c(&arena, 1, 0, 0); c = *a; ASSERT_EQ(c.length(), 1); ASSERT_EQ(c.at(0), 1); } // Copy to embedded/resource { ResourceMark rm; WithEmbeddedArray c(1); c._a = *a; ASSERT_EQ(c._a.length(), 1); ASSERT_EQ(c._a.at(0), 1); } // Copy to embedded/arena { Arena arena(mtTest); WithEmbeddedArray c(&arena, 1); c._a = *a; ASSERT_EQ(c._a.length(), 1); ASSERT_EQ(c._a.at(0), 1); } } // Supported by all GrowableArrays enum TestEnum { Append, Clear, Capacity, Iterator }; template static void do_test(ArrayClass* a, TestEnum test) { switch (test) { case Append: test_append(a); break; case Clear: test_clear(a); break; case Capacity: test_capacity(a); break; case Iterator: test_iterator(a); break; default: fatal("Missing dispatch"); break; } } // Only supported by GrowableArrays without CHeap data arrays enum TestNoCHeapEnum { Copy1, Assignment1, }; template static void do_test(ArrayClass* a, TestNoCHeapEnum test) { switch (test) { case Copy1: test_copy1(a); break; case Assignment1: test_assignment1(a); break; default: fatal("Missing dispatch"); break; } } enum ModifyEnum { Append1, Append1Clear, Append1ClearAndDeallocate, NoModify }; template static void do_modify(ArrayClass* a, ModifyEnum modify) { switch (modify) { case Append1: a->append(1); break; case Append1Clear: a->append(1); a->clear(); break; case Append1ClearAndDeallocate: a->append(1); a->clear_and_deallocate(); break; case NoModify: // Nothing to do break; default: fatal("Missing dispatch"); break; } } static const int Max0 = 0; static const int Max1 = 1; template static void modify_and_test(ArrayClass* array, ModifyEnum modify, T test) { do_modify(array, modify); do_test(array, test); } template static void with_no_cheap_array(int max, ModifyEnum modify, T test) { // Resource/Resource allocated { ResourceMark rm; GrowableArray* a = new GrowableArray(max); modify_and_test(a, modify, test); } // Resource/Arena allocated // Combination not supported // CHeap/Resource allocated // Combination not supported // CHeap/Arena allocated // Combination not supported // Stack/Resource allocated { ResourceMark rm; GrowableArray a(max); modify_and_test(&a, modify, test); } // Stack/Arena allocated { Arena arena(mtTest); GrowableArray a(&arena, max, 0, 0); modify_and_test(&a, modify, test); } // Embedded/Resource allocated { ResourceMark rm; WithEmbeddedArray w(max); modify_and_test(&w._a, modify, test); } // Embedded/Arena allocated { Arena arena(mtTest); WithEmbeddedArray w(&arena, max); modify_and_test(&w._a, modify, test); } } static void with_cheap_array(int max, ModifyEnum modify, TestEnum test) { // Resource/CHeap allocated // Combination not supported // CHeap/CHeap allocated { GrowableArray* a = new (mtTest) GrowableArray(max, mtTest); modify_and_test(a, modify, test); delete a; } // Stack/CHeap allocated { GrowableArray a(max, mtTest); modify_and_test(&a, modify, test); } // Embedded/CHeap allocated { WithEmbeddedArray w(max, mtTest); modify_and_test(&w._a, modify, test); } } static void with_all_types(int max, ModifyEnum modify, TestEnum test) { with_no_cheap_array(max, modify, test); with_cheap_array(max, modify, test); } static void with_all_types_empty(TestEnum test) { with_all_types(Max0, NoModify, test); } static void with_all_types_max_set(TestEnum test) { with_all_types(Max1, NoModify, test); } static void with_all_types_cleared(TestEnum test) { with_all_types(Max1, Append1Clear, test); } static void with_all_types_clear_and_deallocated(TestEnum test) { with_all_types(Max1, Append1ClearAndDeallocate, test); } static void with_all_types_all_0(TestEnum test) { with_all_types_empty(test); with_all_types_max_set(test); with_all_types_cleared(test); with_all_types_clear_and_deallocated(test); } static void with_no_cheap_array_append1(TestNoCHeapEnum test) { with_no_cheap_array(Max0, Append1, test); } }; TEST_VM_F(GrowableArrayTest, append) { with_all_types_all_0(Append); } TEST_VM_F(GrowableArrayTest, clear) { with_all_types_all_0(Clear); } TEST_VM_F(GrowableArrayTest, capacity) { with_all_types_all_0(Capacity); } TEST_VM_F(GrowableArrayTest, iterator) { with_all_types_all_0(Iterator); } TEST_VM_F(GrowableArrayTest, copy) { with_no_cheap_array_append1(Copy1); } TEST_VM_F(GrowableArrayTest, assignment) { with_no_cheap_array_append1(Assignment1); } #ifdef ASSERT TEST_VM_F(GrowableArrayTest, where) { WithEmbeddedArray s(1, mtTest); ASSERT_FALSE(s._a.allocated_on_C_heap()); ASSERT_TRUE(elements_on_C_heap(&s._a)); // Resource/Resource allocated { ResourceMark rm; GrowableArray* a = new GrowableArray(); ASSERT_TRUE(a->allocated_on_res_area()); ASSERT_TRUE(elements_on_resource_area(a)); } // Resource/CHeap allocated // Combination not supported // Resource/Arena allocated // Combination not supported // CHeap/Resource allocated // Combination not supported // CHeap/CHeap allocated { GrowableArray* a = new (mtTest) GrowableArray(0, mtTest); ASSERT_TRUE(a->allocated_on_C_heap()); ASSERT_TRUE(elements_on_C_heap(a)); delete a; } // CHeap/Arena allocated // Combination not supported // Stack/Resource allocated { ResourceMark rm; GrowableArray a(0); ASSERT_TRUE(a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_resource_area(&a)); } // Stack/CHeap allocated { GrowableArray a(0, mtTest); ASSERT_TRUE(a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_C_heap(&a)); } // Stack/Arena allocated { Arena arena(mtTest); GrowableArray a(&arena, 0, 0, 0); ASSERT_TRUE(a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_arena(&a)); } // Embedded/Resource allocated { ResourceMark rm; WithEmbeddedArray w(0); ASSERT_TRUE(w._a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_resource_area(&w._a)); } // Embedded/CHeap allocated { WithEmbeddedArray w(0, mtTest); ASSERT_TRUE(w._a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_C_heap(&w._a)); } // Embedded/Arena allocated { Arena arena(mtTest); WithEmbeddedArray w(&arena, 0); ASSERT_TRUE(w._a.allocated_on_stack_or_embedded()); ASSERT_TRUE(elements_on_arena(&w._a)); } } TEST_VM_ASSERT_MSG(GrowableArrayAssertingTest, copy_with_embedded_cheap, "assert.!on_C_heap... failed: Copying of CHeap arrays not supported") { WithEmbeddedArray s(1, mtTest); // Intentionally asserts that copy of CHeap arrays are not allowed WithEmbeddedArray c(s); } TEST_VM_ASSERT_MSG(GrowableArrayAssertingTest, assignment_with_embedded_cheap, "assert.!on_C_heap... failed: Assignment of CHeap arrays not supported") { WithEmbeddedArray s(1, mtTest); WithEmbeddedArray c(1, mtTest); // Intentionally asserts that assignment of CHeap arrays are not allowed c = s; } #endif TEST(GrowableArrayCHeap, sanity) { // Stack/CHeap { GrowableArrayCHeap a(0); #ifdef ASSERT ASSERT_TRUE(a.allocated_on_stack_or_embedded()); #endif ASSERT_TRUE(a.is_empty()); a.append(1); ASSERT_FALSE(a.is_empty()); ASSERT_EQ(a.at(0), 1); } // CHeap/CHeap { GrowableArrayCHeap* a = new GrowableArrayCHeap(0); #ifdef ASSERT ASSERT_TRUE(a->allocated_on_C_heap()); #endif ASSERT_TRUE(a->is_empty()); a->append(1); ASSERT_FALSE(a->is_empty()); ASSERT_EQ(a->at(0), 1); delete a; } // CHeap/CHeap - nothrow new operator { GrowableArrayCHeap* a = new (std::nothrow) GrowableArrayCHeap(0); #ifdef ASSERT ASSERT_TRUE(a->allocated_on_C_heap()); #endif ASSERT_TRUE(a->is_empty()); a->append(1); ASSERT_FALSE(a->is_empty()); ASSERT_EQ(a->at(0), 1); delete a; } } TEST(GrowableArrayCHeap, find_if) { struct Element { int value; }; GrowableArrayCHeap array; array.push({1}); array.push({2}); array.push({3}); { int index = array.find_if([&](const Element& elem) { return elem.value == 1; }); ASSERT_EQ(index, 0); } { int index = array.find_if([&](const Element& elem) { return elem.value > 1; }); ASSERT_EQ(index, 1); } { int index = array.find_if([&](const Element& elem) { return elem.value == 4; }); ASSERT_EQ(index, -1); } } TEST(GrowableArrayCHeap, find_from_end_if) { struct Element { int value; }; GrowableArrayCHeap array; array.push({1}); array.push({2}); array.push({3}); { int index = array.find_from_end_if([&](const Element& elem) { return elem.value == 1; }); ASSERT_EQ(index, 0); } { int index = array.find_from_end_if([&](const Element& elem) { return elem.value > 1; }); ASSERT_EQ(index, 2); } { int index = array.find_from_end_if([&](const Element& elem) { return elem.value == 4; }); ASSERT_EQ(index, -1); } } TEST(GrowableArrayCHeap, returning_references_works_as_expected) { GrowableArrayCHeap arr(8, 8, -1); // Pre-fill with 8 -1s int& x = arr.at_grow(9, -1); EXPECT_EQ(-1, arr.at(9)); EXPECT_EQ(-1, x); x = 2; EXPECT_EQ(2, arr.at(9)); int& x2 = arr.top(); EXPECT_EQ(2, arr.at(9)); x2 = 5; EXPECT_EQ(5, arr.at(9)); int y = arr.at_grow(10, -1); EXPECT_EQ(-1, arr.at(10)); y = arr.top(); EXPECT_EQ(-1, arr.at(10)); GrowableArrayCHeap arr2(1, 1, -1); int& first = arr2.first(); int& last = arr2.last(); EXPECT_EQ(-1, first); EXPECT_EQ(-1, last); first = 5; EXPECT_EQ(5, first); EXPECT_EQ(5, last); }