From 7719d9a23dcd7bdc59331ddf26f07c5623398c22 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Fri, 8 May 2026 14:32:44 -0500 Subject: [PATCH 1/4] 8384108: Update runtime/clone tests with value classes --- .../runtime/clone/CloneWithValueFields.java | 228 ++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 test/hotspot/jtreg/runtime/clone/CloneWithValueFields.java diff --git a/test/hotspot/jtreg/runtime/clone/CloneWithValueFields.java b/test/hotspot/jtreg/runtime/clone/CloneWithValueFields.java new file mode 100644 index 00000000000..553d7040b42 --- /dev/null +++ b/test/hotspot/jtreg/runtime/clone/CloneWithValueFields.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2026, 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. + */ + +/* + * @test + * @bug 8384108 + * @summary Verify clone works correctly with value class fields and arrays + * @run main CloneWithValueFields + */ +public class CloneWithValueFields { + + public static void main(String[] args) throws Exception { + testBasicClone(); + testCloneIndependence(); + testNullValueFields(); + testInheritedValueFields(); + testValueArrayClone(); + testMixedArrayClone(); + testCloneGCSurvival(); + } + + // Clone an identity object with various value class fields + static void testBasicClone() throws Exception { + Container orig = new Container(Integer.valueOf(42), Long.valueOf(100L), + Double.valueOf(3.14), Short.valueOf((short)7)); + Container copy = orig.clone(); + + assertEquals(orig.intVal, copy.intVal, "intVal"); + assertEquals(orig.longVal, copy.longVal, "longVal"); + assertEquals(orig.doubleVal, copy.doubleVal, "doubleVal"); + assertEquals(orig.shortVal, copy.shortVal, "shortVal"); + } + + // Verify clone produces an independent copy + static void testCloneIndependence() throws Exception { + Container orig = new Container(Integer.valueOf(42), Long.valueOf(100L), + Double.valueOf(3.14), Short.valueOf((short)7)); + Container copy = orig.clone(); + + orig.intVal = Integer.valueOf(99); + orig.longVal = Long.valueOf(200L); + orig.doubleVal = Double.valueOf(2.71); + orig.shortVal = Short.valueOf((short)0); + + assertEquals(copy.intVal, Integer.valueOf(42), "intVal independence"); + assertEquals(copy.longVal, Long.valueOf(100L), "longVal independence"); + assertEquals(copy.doubleVal, Double.valueOf(3.14), "doubleVal independence"); + assertEquals(copy.shortVal, Short.valueOf((short)7), "shortVal independence"); + } + + // Clone with null value class fields + static void testNullValueFields() throws Exception { + Container orig = new Container(null, null, null, null); + Container copy = orig.clone(); + + if (copy.intVal != null) throw new RuntimeException("intVal should be null"); + if (copy.longVal != null) throw new RuntimeException("longVal should be null"); + if (copy.doubleVal != null) throw new RuntimeException("doubleVal should be null"); + if (copy.shortVal != null) throw new RuntimeException("shortVal should be null"); + } + + // Clone with value fields at different inheritance levels + static void testInheritedValueFields() throws Exception { + Child orig = new Child(Integer.valueOf(1), Long.valueOf(2L), + Float.valueOf(3.0f), Byte.valueOf((byte)4)); + Child copy = orig.clone(); + + assertEquals(orig.intVal, copy.intVal, "parent intVal"); + assertEquals(orig.longVal, copy.longVal, "parent longVal"); + assertEquals(orig.floatVal, copy.floatVal, "child floatVal"); + assertEquals(orig.byteVal, copy.byteVal, "child byteVal"); + + orig.intVal = Integer.valueOf(99); + orig.floatVal = Float.valueOf(99.0f); + assertEquals(copy.intVal, Integer.valueOf(1), "parent intVal independence"); + assertEquals(copy.floatVal, Float.valueOf(3.0f), "child floatVal independence"); + } + + // Clone arrays of value class elements + static void testValueArrayClone() { + Integer[] intArr = new Integer[]{1, 2, 3, 4, 5}; + Integer[] intCopy = intArr.clone(); + assertArrayIndependent(intArr, intCopy, "Integer array"); + + Long[] longArr = new Long[]{10L, 20L, 30L}; + Long[] longCopy = longArr.clone(); + assertArrayIndependent(longArr, longCopy, "Long array"); + + Double[] dblArr = new Double[]{1.1, 2.2, 3.3}; + Double[] dblCopy = dblArr.clone(); + assertArrayIndependent(dblArr, dblCopy, "Double array"); + } + + // Clone array containing mixed value and identity objects + static void testMixedArrayClone() { + Object[] orig = new Object[]{Integer.valueOf(1), Long.valueOf(2L), + "three", new Object(), null}; + Object[] copy = orig.clone(); + + if (orig == copy) + throw new RuntimeException("Mixed array clone returned same reference"); + if (orig.length != copy.length) + throw new RuntimeException("Mixed array length mismatch"); + for (int i = 0; i < orig.length; i++) { + if (orig[i] == null) { + if (copy[i] != null) + throw new RuntimeException("Mixed array null mismatch at " + i); + } else if (orig[i] instanceof Number) { + if (!orig[i].equals(copy[i])) + throw new RuntimeException("Mixed array Number mismatch at " + i); + } else if (orig[i] instanceof String) { + if (!orig[i].equals(copy[i])) + throw new RuntimeException("Mixed array String mismatch at " + i); + } else { + if (orig[i] != copy[i]) + throw new RuntimeException("Mixed array Object identity mismatch at " + i); + } + } + } + + // Cloned objects with value fields survive GC + static void testCloneGCSurvival() throws Exception { + Container[] copies = new Container[1000]; + for (int i = 0; i < copies.length; i++) { + Container orig = new Container(Integer.valueOf(i), Long.valueOf(i * 10L), + Double.valueOf(i * 1.1), Short.valueOf((short)(i % 100))); + copies[i] = orig.clone(); + } + + System.gc(); + + for (int i = 0; i < copies.length; i++) { + assertEquals(copies[i].intVal, Integer.valueOf(i), "gc intVal " + i); + assertEquals(copies[i].longVal, Long.valueOf(i * 10L), "gc longVal " + i); + assertEquals(copies[i].doubleVal, Double.valueOf(i * 1.1), "gc doubleVal " + i); + assertEquals(copies[i].shortVal, Short.valueOf((short)(i % 100)), "gc shortVal " + i); + } + } + + static void assertEquals(Object actual, Object expected, String msg) { + if (!java.util.Objects.equals(actual, expected)) { + throw new RuntimeException(msg + ": expected " + expected + " got " + actual); + } + } + + static void assertArrayIndependent(Object[] orig, Object[] copy, String name) { + if (orig == copy) + throw new RuntimeException(name + " clone returned same reference"); + if (orig.length != copy.length) + throw new RuntimeException(name + " length mismatch"); + for (int i = 0; i < orig.length; i++) { + if (!orig[i].equals(copy[i])) + throw new RuntimeException(name + " element " + i + " mismatch"); + } + Object saved = copy[0]; + orig[0] = null; + if (copy[0] == null) + throw new RuntimeException(name + " clone not independent"); + orig[0] = saved; + } + + static class Container implements Cloneable { + Integer intVal; + Long longVal; + Double doubleVal; + Short shortVal; + + Container(Integer i, Long l, Double d, Short s) { + this.intVal = i; + this.longVal = l; + this.doubleVal = d; + this.shortVal = s; + } + + public Container clone() throws CloneNotSupportedException { + return (Container) super.clone(); + } + } + + static class Parent implements Cloneable { + Integer intVal; + Long longVal; + + Parent(Integer i, Long l) { + this.intVal = i; + this.longVal = l; + } + + public Parent clone() throws CloneNotSupportedException { + return (Parent) super.clone(); + } + } + + static class Child extends Parent { + Float floatVal; + Byte byteVal; + + Child(Integer i, Long l, Float f, Byte b) { + super(i, l); + this.floatVal = f; + this.byteVal = b; + } + + public Child clone() throws CloneNotSupportedException { + return (Child) super.clone(); + } + } +} From b70c0cc73e8d736a2c831a803be215b8fbaa3bcb Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Mon, 11 May 2026 13:09:13 -0500 Subject: [PATCH 2/4] 8384108: Add custom value class clone tests --- .../jtreg/runtime/clone/CloneValueClass.java | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 test/hotspot/jtreg/runtime/clone/CloneValueClass.java diff --git a/test/hotspot/jtreg/runtime/clone/CloneValueClass.java b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java new file mode 100644 index 00000000000..74b397d598d --- /dev/null +++ b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2026, 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. + */ + +/* + * @test + * @bug 8384108 + * @summary Verify clone behavior with custom value classes + * @modules java.base/java.lang:open + * @enablePreview + * @run main CloneValueClass + */ +public class CloneValueClass { + + static value class Point { + int x; + int y; + Point(int x, int y) { + this.x = x; + this.y = y; + } + } + + static value class MixedValue { + int id; + String name; + MixedValue(int id, String name) { + this.id = id; + this.name = name; + } + } + + static class Holder implements Cloneable { + Point point; + MixedValue mixed; + int primitive; + + Holder(Point p, MixedValue m, int i) { + this.point = p; + this.mixed = m; + this.primitive = i; + } + + public Holder clone() throws CloneNotSupportedException { + return (Holder) super.clone(); + } + } + + public static void main(String[] args) throws Exception { + testValueObjectCloneThrows(); + testCloneHolderWithValueFields(); + testCloneIndependence(); + testValueArrayClone(); + testNullValueFields(); + } + + // Cloning a value object directly should throw CloneNotSupportedException + static void testValueObjectCloneThrows() throws Exception { + Point p = new Point(1, 2); + try { + java.lang.reflect.Method m = Object.class.getDeclaredMethod("clone"); + m.setAccessible(true); + m.invoke(p); + throw new RuntimeException("Expected CloneNotSupportedException for value object"); + } catch (java.lang.reflect.InvocationTargetException e) { + if (!(e.getCause() instanceof CloneNotSupportedException)) { + throw new RuntimeException("Expected CloneNotSupportedException, got " + + e.getCause().getClass().getName()); + } + } + } + + // Clone an identity object holding custom value class fields + static void testCloneHolderWithValueFields() throws Exception { + Point p = new Point(10, 20); + MixedValue m = new MixedValue(42, "hello"); + Holder orig = new Holder(p, m, 7); + Holder copy = orig.clone(); + + if (orig.point.x != copy.point.x || orig.point.y != copy.point.y) + throw new RuntimeException("Point field not cloned correctly"); + if (orig.mixed.id != copy.mixed.id || !orig.mixed.name.equals(copy.mixed.name)) + throw new RuntimeException("MixedValue field not cloned correctly"); + if (orig.primitive != copy.primitive) + throw new RuntimeException("Primitive field not cloned correctly"); + } + + // Modifying original value fields doesn't affect clone + static void testCloneIndependence() throws Exception { + Holder orig = new Holder(new Point(1, 2), new MixedValue(3, "test"), 5); + Holder copy = orig.clone(); + + orig.point = new Point(99, 99); + orig.mixed = new MixedValue(0, "changed"); + orig.primitive = 0; + + if (copy.point.x != 1 || copy.point.y != 2) + throw new RuntimeException("Point clone not independent"); + if (copy.mixed.id != 3 || !copy.mixed.name.equals("test")) + throw new RuntimeException("MixedValue clone not independent"); + if (copy.primitive != 5) + throw new RuntimeException("Primitive clone not independent"); + } + + // Clone arrays of custom value class elements + static void testValueArrayClone() { + Point[] orig = new Point[]{new Point(1, 2), new Point(3, 4), new Point(5, 6)}; + Point[] copy = orig.clone(); + + if (orig == copy) + throw new RuntimeException("Array clone returned same reference"); + for (int i = 0; i < orig.length; i++) { + if (orig[i].x != copy[i].x || orig[i].y != copy[i].y) + throw new RuntimeException("Array element " + i + " not cloned correctly"); + } + orig[0] = new Point(99, 99); + if (copy[0].x != 1 || copy[0].y != 2) + throw new RuntimeException("Array clone not independent"); + } + + // Clone holder with null value fields + static void testNullValueFields() throws Exception { + Holder orig = new Holder(null, null, 0); + Holder copy = orig.clone(); + + if (copy.point != null) throw new RuntimeException("Point should be null"); + if (copy.mixed != null) throw new RuntimeException("MixedValue should be null"); + } +} From a4d46707b8b2fa0e437421fd2a6b86783aaed9c6 Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Mon, 11 May 2026 13:25:44 -0500 Subject: [PATCH 3/4] 8384108: Add array fields to custom value class clone test --- .../jtreg/runtime/clone/CloneValueClass.java | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/runtime/clone/CloneValueClass.java b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java index 74b397d598d..b2e389cd13d 100644 --- a/test/hotspot/jtreg/runtime/clone/CloneValueClass.java +++ b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java @@ -52,11 +52,15 @@ static value class MixedValue { static class Holder implements Cloneable { Point point; MixedValue mixed; + Point[] pointArray; + MixedValue[] mixedArray; int primitive; - Holder(Point p, MixedValue m, int i) { + Holder(Point p, MixedValue m, Point[] pa, MixedValue[] ma, int i) { this.point = p; this.mixed = m; + this.pointArray = pa; + this.mixedArray = ma; this.primitive = i; } @@ -69,6 +73,7 @@ public static void main(String[] args) throws Exception { testValueObjectCloneThrows(); testCloneHolderWithValueFields(); testCloneIndependence(); + testArrayFieldShallowClone(); testValueArrayClone(); testNullValueFields(); } @@ -89,24 +94,30 @@ static void testValueObjectCloneThrows() throws Exception { } } - // Clone an identity object holding custom value class fields + // Clone an identity object holding custom value class fields and arrays static void testCloneHolderWithValueFields() throws Exception { - Point p = new Point(10, 20); - MixedValue m = new MixedValue(42, "hello"); - Holder orig = new Holder(p, m, 7); + Point[] pa = new Point[]{new Point(5, 6), new Point(7, 8)}; + MixedValue[] ma = new MixedValue[]{new MixedValue(10, "a"), new MixedValue(20, "b")}; + Holder orig = new Holder(new Point(1, 2), new MixedValue(42, "hello"), pa, ma, 7); Holder copy = orig.clone(); if (orig.point.x != copy.point.x || orig.point.y != copy.point.y) throw new RuntimeException("Point field not cloned correctly"); if (orig.mixed.id != copy.mixed.id || !orig.mixed.name.equals(copy.mixed.name)) throw new RuntimeException("MixedValue field not cloned correctly"); + if (copy.pointArray.length != 2 || copy.pointArray[0].x != 5) + throw new RuntimeException("Point array not cloned correctly"); + if (copy.mixedArray.length != 2 || !copy.mixedArray[1].name.equals("b")) + throw new RuntimeException("MixedValue array not cloned correctly"); if (orig.primitive != copy.primitive) throw new RuntimeException("Primitive field not cloned correctly"); } // Modifying original value fields doesn't affect clone static void testCloneIndependence() throws Exception { - Holder orig = new Holder(new Point(1, 2), new MixedValue(3, "test"), 5); + Point[] pa = new Point[]{new Point(1, 1)}; + MixedValue[] ma = new MixedValue[]{new MixedValue(1, "x")}; + Holder orig = new Holder(new Point(1, 2), new MixedValue(3, "test"), pa, ma, 5); Holder copy = orig.clone(); orig.point = new Point(99, 99); @@ -121,7 +132,26 @@ static void testCloneIndependence() throws Exception { throw new RuntimeException("Primitive clone not independent"); } - // Clone arrays of custom value class elements + // Shallow clone shares array references + static void testArrayFieldShallowClone() throws Exception { + Point[] pa = new Point[]{new Point(1, 2), new Point(3, 4)}; + MixedValue[] ma = new MixedValue[]{new MixedValue(10, "a")}; + Holder orig = new Holder(new Point(0, 0), new MixedValue(0, ""), pa, ma, 0); + Holder copy = orig.clone(); + + // Shallow clone — array references are shared + if (orig.pointArray != copy.pointArray) + throw new RuntimeException("Shallow clone should share pointArray reference"); + if (orig.mixedArray != copy.mixedArray) + throw new RuntimeException("Shallow clone should share mixedArray reference"); + + // Replacing array on original doesn't affect copy + orig.pointArray = new Point[]{new Point(99, 99)}; + if (copy.pointArray.length != 2 || copy.pointArray[0].x != 1) + throw new RuntimeException("Array field replacement should not affect clone"); + } + + // Clone arrays of custom value class elements directly static void testValueArrayClone() { Point[] orig = new Point[]{new Point(1, 2), new Point(3, 4), new Point(5, 6)}; Point[] copy = orig.clone(); @@ -137,12 +167,14 @@ static void testValueArrayClone() { throw new RuntimeException("Array clone not independent"); } - // Clone holder with null value fields + // Clone holder with null value fields and null arrays static void testNullValueFields() throws Exception { - Holder orig = new Holder(null, null, 0); + Holder orig = new Holder(null, null, null, null, 0); Holder copy = orig.clone(); if (copy.point != null) throw new RuntimeException("Point should be null"); if (copy.mixed != null) throw new RuntimeException("MixedValue should be null"); + if (copy.pointArray != null) throw new RuntimeException("pointArray should be null"); + if (copy.mixedArray != null) throw new RuntimeException("mixedArray should be null"); } } From 5f280f3b14e126371c424bdcbadfdde3e2acd78c Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Wed, 20 May 2026 15:19:06 -0400 Subject: [PATCH 4/4] 8384108: Use @AsValueClass instead of value keyword --- test/hotspot/jtreg/runtime/clone/CloneValueClass.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/runtime/clone/CloneValueClass.java b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java index b2e389cd13d..7db01244df9 100644 --- a/test/hotspot/jtreg/runtime/clone/CloneValueClass.java +++ b/test/hotspot/jtreg/runtime/clone/CloneValueClass.java @@ -25,13 +25,15 @@ * @test * @bug 8384108 * @summary Verify clone behavior with custom value classes + * @library /test/lib * @modules java.base/java.lang:open - * @enablePreview * @run main CloneValueClass */ +import jdk.test.lib.valueclass.AsValueClass; public class CloneValueClass { - static value class Point { + @AsValueClass + static class Point { int x; int y; Point(int x, int y) { @@ -40,7 +42,8 @@ static value class Point { } } - static value class MixedValue { + @AsValueClass + static class MixedValue { int id; String name; MixedValue(int id, String name) {