From 6bfabe45c815c35c4114f7cd70fc0364081abc59 Mon Sep 17 00:00:00 2001 From: Frederic Parain Date: Fri, 26 Jun 2026 09:23:02 -0400 Subject: [PATCH] Various fixes to copyOfSpecialArray --- src/hotspot/share/prims/jvm.cpp | 55 ++++---- .../jdk/internal/value/ValueClass.java | 10 ++ .../valhalla/inlinetypes/InlineTypeArray.java | 2 +- .../inlinetypes/TestArrayFactories.java | 117 ++++++++++++++++++ 4 files changed, 150 insertions(+), 34 deletions(-) diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index c525e592740..a4eded9a54a 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -430,56 +430,45 @@ JVM_END JVM_ENTRY(jarray, JVM_CopyOfSpecialArray(JNIEnv *env, jarray orig, jint from, jint to)) oop o = JNIHandles::resolve_non_null(orig); assert(o->is_array(), "Must be"); - oop array = nullptr; + objArrayOop dest = nullptr; arrayOop org = (arrayOop)o; arrayHandle oh(THREAD, org); ObjArrayKlass* ak = ObjArrayKlass::cast(org->klass()); InlineKlass* vk = InlineKlass::cast(ak->element_klass()); int len = to - from; // length of the new array + int orig_length = org->length(); if (ak->is_null_free_array_klass()) { - if ((len != 0) && (from >= org->length() || to > org->length())) { + if ((len != 0) && (from >= orig_length || to > orig_length)) { THROW_MSG_NULL(vmSymbols::java_lang_IllegalArgumentException(), "Copying of null-free array with uninitialized elements"); } } + if (ak->is_flatArray_klass()) { - // The whole JVM_CopyOfSpecialArray is currently broken. Fix this in a separate bugfix. - int org_length = org->length(); - int copy_len = MIN2(to, org_length) - MIN2(from, org_length); FlatArrayKlass* const fak = FlatArrayKlass::cast(org->klass()); - flatArrayOop dst = fak->allocate_instance(len, CHECK_NULL); + dest = fak->allocate_instance(len, CHECK_NULL); + } else { + const ArrayProperties props = ArrayProperties::Default().with_null_restricted(ak->is_null_free_array_klass()); + dest = oopFactory::new_objArray(vk, len, props, CHECK_NULL); + } + + arrayHandle dh(THREAD, dest); + + int copy_len = MIN2(to, orig_length) - MIN2(from, orig_length); + if (copy_len != 0) { assert(!ak->is_null_free_array_klass() || copy_len == len, "Failed to throw the IllegalArgumentException"); - if (copy_len != 0) { - int start = MIN2(from, org_length - 1); - FlatArrayPayload src_payload(flatArrayOop(oh()), start, fak); - FlatArrayPayload dst_payload(dst, 0, fak); - int end = to < oh()->length() ? to : oh()->length(); - for (int i = from; i < end; i++) { - // Copy a value - src_payload.copy_to(dst_payload); - - // Advance to the next element - src_payload.next_element(); - dst_payload.next_element(); - } + ak->copy_array(oh(), from, dh(), 0, copy_len, CHECK_NULL); + for (int i = copy_len; i < len; i++) { + ((objArrayOop)dh())->obj_at_put(i, nullptr); } - array = dst; } else { - const ArrayProperties props = ArrayProperties::Default().with_null_restricted(ak->is_null_free_array_klass()); - - array = oopFactory::new_objArray(vk, len, props, CHECK_NULL); - int end = to < oh()->length() ? to : oh()->length(); - for (int i = from; i < end; i++) { - if (i < ((objArrayOop)oh())->length()) { - oop val = ((objArrayOop)oh())->obj_at(i, CHECK_NULL); - ((objArrayOop)array)->obj_at_put(i - from, val); - } else { - assert(!ak->is_null_free_array_klass(), "Must be a nullable array"); - ((objArrayOop)array)->obj_at_put(i - from, nullptr); - } + for (int i = 0; i < len; i++) { + ((objArrayOop)dh())->obj_at_put(i, nullptr); } } - return (jarray) JNIHandles::make_local(THREAD, array); + + return (jarray) JNIHandles::make_local(THREAD, dh()); + JVM_END #ifdef ASSERT diff --git a/src/java.base/share/classes/jdk/internal/value/ValueClass.java b/src/java.base/share/classes/jdk/internal/value/ValueClass.java index 02ade697a78..ac49e87f9de 100644 --- a/src/java.base/share/classes/jdk/internal/value/ValueClass.java +++ b/src/java.base/share/classes/jdk/internal/value/ValueClass.java @@ -172,7 +172,16 @@ public static boolean isFlatArray(Object[] array) { @IntrinsicCandidate private static native boolean isFlatArray0(Object[] array); + @ForceInline + private static void validateSpecialArray(Object[] array) { + Objects.requireNonNull(array); + if (!isConcreteValueClass(array.getClass().getComponentType())) { + throw new IllegalArgumentException("Element class is not a concrete value class"); + } + } + public static Object[] copyOfSpecialArray(Object[] array, int newLength) { + validateSpecialArray(array); if (newLength < 0) { throw new NegativeArraySizeException("" + newLength); } @@ -180,6 +189,7 @@ public static Object[] copyOfSpecialArray(Object[] array, int newLength) { } public static Object[] copyOfRangeSpecialArray(Object[] array, int from, int to) { + validateSpecialArray(array); int length = array.length; if (from < 0 || from > length) { throw new ArrayIndexOutOfBoundsException("source index " + from + " out of bounds for object array[" + length + "]"); diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java index 7e93eb56f32..2be03354c31 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/InlineTypeArray.java @@ -636,7 +636,7 @@ void testUtilArraysOnNullableAtomicArrays() { expected3b[2] = null; checkArrayElementsEqual(exceedingRangeCopy, expected3b); - // Range starting after the end of the original array, must suceed for nullable arrays + // Range starting after the end of the original array, must succeed for nullable arrays MyInt[] farRangeCopy = (MyInt[]) Arrays.copyOfRange(myInts, myInts.length, myInts.length + 1); MyInt[] expected3c = (MyInt[])ValueClass.newNullableAtomicArray(MyInt.class, 1); expected3c[0] = null; diff --git a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestArrayFactories.java b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestArrayFactories.java index 3baad760887..d837a21d2c1 100644 --- a/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestArrayFactories.java +++ b/test/hotspot/jtreg/runtime/valhalla/inlinetypes/TestArrayFactories.java @@ -294,6 +294,123 @@ static void test_25() { } catch (NullPointerException e) { } } + // Test arguments validation in copyOfSpecialArray() + + static void test_26() { + try { + var a = ValueClass.copyOfSpecialArray(null, 0); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) {} + } + + static void test_27() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 1, new MyVal()); + try { + var a = ValueClass.copyOfSpecialArray(array, -1); + throw new RuntimeException("Missing NegativeArraySizeException"); + } catch (NegativeArraySizeException e) {} + } + + static void test_28() { + var array = new Object[1]; + try { + var a = ValueClass.copyOfSpecialArray(array, 0); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_29() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfSpecialArray(array, 4); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_30() { + var array = ValueClass.newNullRestrictedAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfSpecialArray(array, 4); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_31() { + var array = ValueClass.newNullableAtomicArray(MyVal.class, 2); + try { + var a = ValueClass.copyOfSpecialArray(array, 4); + Asserts.assertEquals(a.length, 4); + } catch (Throwable e) { + throw new RuntimeException("Unexpected exception " + e); + } + } + + // Test arguments validation in copyOfRangeSpecialArray() + + static void test_32() { + try { + var a = ValueClass.copyOfRangeSpecialArray(null, 0, 1); + throw new RuntimeException("Missing NullPointerException"); + } catch (NullPointerException e) {} + } + + static void test_33() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, -1, 1); + throw new RuntimeException("Missing ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) {} + } + + static void test_34() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 3, 4); + throw new RuntimeException("Missing ArrayIndexOutOfBoundsException"); + } catch (ArrayIndexOutOfBoundsException e) {} + } + + static void test_35() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 1, 0); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_36() { + var array = ValueClass.newNullRestrictedNonAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 2, 4); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_37() { + var array = ValueClass.newNullRestrictedAtomicArray(MyVal.class, 2, new MyVal()); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 2, 4); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + + static void test_38() { + var array = ValueClass.newNullableAtomicArray(MyVal.class, 2); + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 2, 4); + } catch (Throwable e) { + throw new RuntimeException("Unexpected exception " + e); + } + } + + static void test_39() { + var array = new Object[1]; + try { + var a = ValueClass.copyOfRangeSpecialArray(array, 0, 0); + throw new RuntimeException("Missing IllegalArgumentException"); + } catch (IllegalArgumentException e) {} + } + public static void main(String[] args) { var test = new TestArrayFactories(); Class c = test.getClass();