1 /*
  2  * Copyright (c) 2023, 2025, 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  * @run junit/othervm -Xshare:off -XX:+UseAltSubstitutabilityMethod SubstitutabilityTest
 32  */
 33 
 34 import java.lang.reflect.Method;
 35 import java.util.stream.Stream;
 36 
 37 import jdk.internal.value.ValueClass;
 38 import jdk.internal.vm.annotation.NullRestricted;
 39 import jdk.internal.vm.annotation.Strict;
 40 
 41 import org.junit.jupiter.api.Test;
 42 import org.junit.jupiter.params.ParameterizedTest;
 43 import org.junit.jupiter.params.provider.Arguments;
 44 import org.junit.jupiter.params.provider.MethodSource;
 45 
 46 import static org.junit.jupiter.api.Assertions.*;
 47 
 48 public class SubstitutabilityTest {
 49     static value class Point {
 50         public int x;
 51         public int y;
 52         Point(int x, int y) {
 53             this.x = x;
 54             this.y = y;
 55         }
 56     }
 57 
 58     static value class Line {
 59         @NullRestricted  @Strict
 60         Point p1;
 61         @NullRestricted  @Strict
 62         Point p2;
 63 
 64         Line(Point p1, Point p2) {
 65             this.p1 = p1;
 66             this.p2 = p2;
 67         }
 68         Line(int x1, int y1, int x2, int y2) {
 69             this(new Point(x1, y1), new Point(x2, y2));
 70         }
 71     }
 72 
 73     // contains null-reference and null-restricted fields
 74     static value class MyValue {
 75         MyValue2 v1;
 76         @NullRestricted  @Strict
 77         MyValue2 v2;
 78         public MyValue(MyValue2 v1, MyValue2 v2) {
 79             this.v1 = v1;
 80             this.v2 = v2;
 81         }
 82     }
 83 
 84     static value class MyValue2 {
 85         static int cnt = 0;
 86         int x;
 87         MyValue2(int x) {
 88             this.x = x;
 89         }
 90     }
 91 
 92     static value class MyFloat {
 93         public static float NaN1 = Float.intBitsToFloat(0x7ff00001);
 94         public static float NaN2 = Float.intBitsToFloat(0x7ff00002);
 95         float x;
 96         MyFloat(float x) {
 97             this.x = x;
 98         }
 99         public String toString() {
100             return Float.toString(x);
101         }
102     }
103 
104     static value class MyDouble {
105         public static double NaN1 = Double.longBitsToDouble(0x7ff0000000000001L);
106         public static double NaN2 = Double.longBitsToDouble(0x7ff0000000000002L);
107         double x;
108         MyDouble(double x) {
109             this.x = x;
110         }
111         public String toString() {
112             return Double.toString(x);
113         }
114     }
115 
116     static Stream<Arguments> substitutableCases() {
117         Point p1 = new Point(10, 10);
118         Point p2 = new Point(20, 20);
119         Line l1 = new Line(p1, p2);
120         MyValue v1 = new MyValue(null, new MyValue2(0));
121         MyValue v2 = new MyValue(new MyValue2(2), new MyValue2(3));
122         MyValue2 value2 = new MyValue2(2);
123         MyValue2 value3 = new MyValue2(3);
124         MyValue[] va = new MyValue[1];
125         return Stream.of(
126                 Arguments.of(new MyFloat(1.0f), new MyFloat(1.0f)),
127                 Arguments.of(new MyDouble(1.0), new MyDouble(1.0)),
128                 Arguments.of(new MyFloat(Float.NaN), new MyFloat(Float.NaN)),
129                 Arguments.of(new MyDouble(Double.NaN), new MyDouble(Double.NaN)),
130                 Arguments.of(p1, new Point(10, 10)),
131                 Arguments.of(p2, new Point(20, 20)),
132                 Arguments.of(l1, new Line(10,10, 20,20)),
133                 Arguments.of(v2, new MyValue(value2, value3)),
134                 Arguments.of(va[0], null)
135         );
136     }
137 
138     @ParameterizedTest
139     @MethodSource("substitutableCases")
140     public void substitutableTest(Object a, Object b) {
141         assertTrue(isSubstitutable(a, b));
142     }
143 
144     static Stream<Arguments> notSubstitutableCases() {
145         return Stream.of(
146                 Arguments.of(new MyFloat(1.0f), new MyFloat(2.0f)),
147                 Arguments.of(new MyDouble(1.0), new MyDouble(2.0)),
148                 Arguments.of(new MyFloat(MyFloat.NaN1), new MyFloat(MyFloat.NaN2)),
149                 Arguments.of(new MyDouble(MyDouble.NaN1), new MyDouble(MyDouble.NaN2)),
150                 Arguments.of(new Point(10, 10), new Point(20, 20)),
151                 /*
152                  * Verify ValueObjectMethods::isSubstitutable that does not
153                  * throw an exception if any one of parameter is null or if
154                  * the parameters are of different types.
155                  */
156                 Arguments.of(new Point(10, 10), Integer.valueOf(10)),
157                 Arguments.of(Integer.valueOf(10), Integer.valueOf(20))
158         );
159     }
160 
161     @ParameterizedTest
162     @MethodSource("notSubstitutableCases")
163     public void notSubstitutableTest(Object a, Object b) {
164         assertFalse(isSubstitutable(a, b));
165     }
166 
167     @Test
168     public void nullArguments() {
169         assertTrue(isSubstitutable(null, null));
170     }
171 
172     private static final Method IS_SUBSTITUTABLE;
173     static {
174         Method m = null;
175         try {
176             Class<?> c = Class.forName("java.lang.runtime.ValueObjectMethods");
177             m = c.getDeclaredMethod("isSubstitutable", Object.class, Object.class);
178             m.setAccessible(true);
179         } catch (ReflectiveOperationException e) {
180             throw new RuntimeException(e);
181         }
182         IS_SUBSTITUTABLE = m;
183     }
184     private static boolean isSubstitutable(Object a, Object b) {
185         try {
186             return (boolean) IS_SUBSTITUTABLE.invoke(null, a, b);
187         } catch (ReflectiveOperationException e) {
188             throw new RuntimeException(e);
189         }
190     }
191 }