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 }