1 /*
2 * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @modules java.base/java.lang.runtime:open
27 * java.base/jdk.internal.value
28 * java.base/jdk.internal.vm.annotation
29 * @enablePreview
30 * @run junit/othervm SubstitutabilityTest
31 */
32
33 import java.lang.reflect.Method;
34 import java.util.stream.Stream;
35
36 import jdk.internal.value.ValueClass;
37 import jdk.internal.vm.annotation.NullRestricted;
38
39 import org.junit.jupiter.api.Test;
40 import org.junit.jupiter.params.ParameterizedTest;
41 import org.junit.jupiter.params.provider.Arguments;
42 import org.junit.jupiter.params.provider.MethodSource;
43
44 import static org.junit.jupiter.api.Assertions.*;
45
46 public class SubstitutabilityTest {
47 static value class Point {
48 public int x;
49 public int y;
50 Point(int x, int y) {
51 this.x = x;
52 this.y = y;
53 }
54 }
55
56 static value class Line {
57 @NullRestricted
58 Point p1;
59 @NullRestricted
60 Point p2;
61
62 Line(Point p1, Point p2) {
63 this.p1 = p1;
64 this.p2 = p2;
65 }
66 Line(int x1, int y1, int x2, int y2) {
67 this(new Point(x1, y1), new Point(x2, y2));
68 }
69 }
70
71 // contains null-reference and null-restricted fields
72 static value class MyValue {
73 MyValue2 v1;
74 @NullRestricted
75 MyValue2 v2;
76 public MyValue(MyValue2 v1, MyValue2 v2) {
77 this.v1 = v1;
78 this.v2 = v2;
79 }
80 }
81
82 static value class MyValue2 {
83 static int cnt = 0;
84 int x;
85 MyValue2(int x) {
86 this.x = x;
87 }
88 }
89
90 static value class MyFloat {
91 public static float NaN1 = Float.intBitsToFloat(0x7ff00001);
92 public static float NaN2 = Float.intBitsToFloat(0x7ff00002);
93 float x;
94 MyFloat(float x) {
95 this.x = x;
96 }
97 public String toString() {
98 return Float.toString(x);
99 }
100 }
101
102 static value class MyDouble {
103 public static double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L);
104 public static double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L);
105 double x;
106 MyDouble(double x) {
107 this.x = x;
108 }
109 public String toString() {
110 return Double.toString(x);
111 }
112 }
113
114 static Stream<Arguments> substitutableCases() {
115 Point p1 = new Point(10, 10);
116 Point p2 = new Point(20, 20);
117 Line l1 = new Line(p1, p2);
118 MyValue v1 = new MyValue(null, new MyValue2(0));
119 MyValue v2 = new MyValue(new MyValue2(2), new MyValue2(3));
120 MyValue2 value2 = new MyValue2(2);
121 MyValue2 value3 = new MyValue2(3);
122 MyValue[] va = new MyValue[1];
123 return Stream.of(
124 Arguments.of(new MyFloat(1.0f), new MyFloat(1.0f)),
125 Arguments.of(new MyDouble(1.0), new MyDouble(1.0)),
126 Arguments.of(new MyFloat(Float.NaN), new MyFloat(Float.NaN)),
127 Arguments.of(new MyDouble(Double.NaN), new MyDouble(Double.NaN)),
128 Arguments.of(p1, new Point(10, 10)),
129 Arguments.of(p2, new Point(20, 20)),
130 Arguments.of(l1, new Line(10,10, 20,20)),
131 Arguments.of(v2, new MyValue(value2, value3))
132 );
133 }
134
135 @ParameterizedTest
136 @MethodSource("substitutableCases")
137 public void substitutableTest(Object a, Object b) {
138 assertTrue(isSubstitutable(a, b));
139 }
140
141 static Stream<Arguments> notSubstitutableCases() {
142 return Stream.of(
143 Arguments.of(new MyFloat(1.0f), new MyFloat(2.0f)),
144 Arguments.of(new MyDouble(1.0), new MyDouble(2.0)),
145 Arguments.of(new MyFloat(MyFloat.NaN1), new MyFloat(MyFloat.NaN2)),
146 Arguments.of(new MyDouble(MyDouble.NaN1), new MyDouble(MyDouble.NaN2)),
147 Arguments.of(new Point(10, 10), new Point(20, 20)),
148 Arguments.of(Integer.valueOf(10), Integer.valueOf(20))
149 );
150 }
151
152 @ParameterizedTest
153 @MethodSource("notSubstitutableCases")
154 public void notSubstitutableTest(Object a, Object b) {
155 assertFalse(isSubstitutable(a, b));
156 }
157
158 private static final Method IS_SUBSTITUTABLE;
159 static {
160 Method m = null;
161 try {
162 Class<?> c = Class.forName("java.lang.runtime.ValueObjectMethods");
163 m = c.getDeclaredMethod("isSubstitutable", Object.class, Object.class);
164 m.setAccessible(true);
165 } catch (ReflectiveOperationException e) {
166 throw new RuntimeException(e);
167 }
168 IS_SUBSTITUTABLE = m;
169 }
170 private static boolean isSubstitutable(Object a, Object b) {
171 try {
172 return (boolean) IS_SUBSTITUTABLE.invoke(null, a, b);
173 } catch (ReflectiveOperationException e) {
174 throw new RuntimeException(e);
175 }
176 }
177
178 static value class IntValue {
179 int value;
180
181 static final int[] EDGE_CASES = {
182 0, -1, 1,
183 Integer.MIN_VALUE, Integer.MAX_VALUE
184 };
185
186 public IntValue(int index) {
187 value = EDGE_CASES[index];
188 }
189
190 public String toString() {
191 return "IntValue(" + value +
192 ", bits=0x" + Integer.toHexString(value) + ")";
193 }
194
195 static boolean cmp(int i, int j) {
196 return EDGE_CASES[i] == EDGE_CASES[j];
197 }
198 }
199
200 static value class NestedValue {
201 IntValue value;
202
203 static final IntValue[] EDGE_CASES = {
204 null, new IntValue(0), new IntValue(1), new IntValue(2),
205 new IntValue(3), new IntValue(0)
206 };
207
208 public NestedValue(int index) {
209 value = EDGE_CASES[index];
210 }
211
212 public String toString() {
213 return "NestedValue(" + value + ")";
214 }
215
216 static boolean cmp(int i, int j) {
217 return EDGE_CASES[i] == EDGE_CASES[j];
218 }
219 }
220
221 public static boolean testNestedValue(NestedValue v1, NestedValue v2) {
222 return v1 == v2;
223 }
224
225 @Test
226 void testNestedValue() {
227 // NestedValue
228 for (int i = 0; i < NestedValue.EDGE_CASES.length; ++i) {
229 for (int j = 0; j < NestedValue.EDGE_CASES.length; ++j) {
230 NestedValue val1 = new NestedValue(i);
231 NestedValue val2 = new NestedValue(j);
232 boolean res = testNestedValue(val1, val2);
233 assertEquals(NestedValue.cmp(i, j), res, () -> val1 + " == " + val2);
234 }
235 }
236 }
237 }