1 /*
2 * Copyright (c) 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. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 import static org.junit.jupiter.api.Assertions.*;
27
28 import jdk.internal.value.ValueClass;
29 import jdk.internal.vm.annotation.LooselyConsistentValue;
30 import jdk.internal.vm.annotation.NullRestricted;
31 import jdk.internal.vm.annotation.Strict;
32 import org.junit.jupiter.params.provider.Arguments;
33 import org.junit.jupiter.params.provider.MethodSource;
34 import org.junit.jupiter.params.ParameterizedTest;
35
36 import java.lang.invoke.MethodHandle;
37 import java.lang.invoke.MethodHandles;
38 import java.lang.invoke.VarHandle;
39 import java.lang.invoke.VarHandle.AccessMode;
40 import java.lang.reflect.Field;
41 import java.lang.reflect.Modifier;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Objects;
45 import java.util.function.BiFunction;
46 import java.util.function.Function;
47
48 /*
49 * @test
50 * @summary Test atomic access modes on var handles for flattened values
51 * @enablePreview
52 * @modules java.base/jdk.internal.value java.base/jdk.internal.vm.annotation
53 * @run junit/othervm -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening FlatVarHandleTest
54 * @run junit/othervm -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening FlatVarHandleTest
55 */
56 public class FlatVarHandleTest {
57
58 interface Pointable { }
59
60 @FunctionalInterface
61 interface TriFunction<A, B, C, R> {
62
63 R apply(A a, B b, C c);
64
65 default <V> TriFunction<A, B, C, V> andThen(
66 Function<? super R, ? extends V> after) {
67 Objects.requireNonNull(after);
68 return (A a, B b, C c) -> after.apply(apply(a, b, c));
69 }
70 }
71
72 @LooselyConsistentValue
73 static value class WeakPoint implements Pointable {
74 short x,y;
75 WeakPoint(int i, int j) { x = (short)i; y = (short)j; }
76
77 static WeakPoint[] makePoints(int len, BiFunction<Class<?>, Integer, Object[]> arrayFactory) {
78 WeakPoint[] array = (WeakPoint[])arrayFactory.apply(WeakPoint.class, len);
79 for (int i = 0; i < len; ++i) {
80 array[i] = new WeakPoint(i, i);
81 }
82 return array;
83 }
84
85 static WeakPoint[] makePoints(int len, Object initval, TriFunction<Class<?>, Integer, Object, Object[]> arrayFactory) {
86 WeakPoint[] array = (WeakPoint[])arrayFactory.apply(WeakPoint.class, len, initval);
87 for (int i = 0; i < len; ++i) {
88 array[i] = new WeakPoint(i, i);
89 }
90 return array;
91 }
92 }
93
94 static class WeakPointHolder {
95 WeakPoint p_i = new WeakPoint(0, 0);
96 static WeakPoint p_s = new WeakPoint(0, 0);
97 @Strict
98 @NullRestricted
99 WeakPoint p_i_nr = new WeakPoint(0, 0);
100 @Strict
101 @NullRestricted
102 static WeakPoint p_s_nr = new WeakPoint(0, 0);
103 }
104
105 static value class StrongPoint implements Pointable {
106 short x,y;
107 StrongPoint(int i, int j) { x = (short)i; y = (short)j; }
108
109 static StrongPoint[] makePoints(int len, BiFunction<Class<?>, Integer, Object[]> arrayFactory) {
110 StrongPoint[] array = (StrongPoint[])arrayFactory.apply(StrongPoint.class, len);
111 for (int i = 0; i < len; ++i) {
112 array[i] = new StrongPoint(i, i);
113 }
114 return array;
115 }
116
117 static StrongPoint[] makePoints(int len, Object initval, TriFunction<Class<?>, Integer, Object, Object[]> arrayFactory) {
118 StrongPoint[] array = (StrongPoint[])arrayFactory.apply(StrongPoint.class, len, initval);
119 for (int i = 0; i < len; ++i) {
120 array[i] = new StrongPoint(i, i);
121 }
122 return array;
123 }
124 }
125
126 static class StrongPointHolder {
127 StrongPoint p_i = new StrongPoint(0, 0);
128 static StrongPoint p_s = new StrongPoint(0, 0);
129 }
130
131 private static List<Arguments> fieldAccessProvider() {
132 try {
133 List<Field> fields = List.of(
134 WeakPointHolder.class.getDeclaredField("p_s"),
135 WeakPointHolder.class.getDeclaredField("p_i"),
136 WeakPointHolder.class.getDeclaredField("p_s_nr"),
137 WeakPointHolder.class.getDeclaredField("p_i_nr"),
138 StrongPointHolder.class.getDeclaredField("p_s"),
139 StrongPointHolder.class.getDeclaredField("p_i"));
140 List<Arguments> arguments = new ArrayList<>();
141 for (AccessMode accessMode : AccessMode.values()) {
142 for (Field field : fields) {
143 boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
144 boolean isWeak = field.getDeclaringClass().equals(WeakPointHolder.class);
145 Object holder = null;
146 if (!isStatic) {
147 holder = isWeak ? new WeakPointHolder() : new StrongPointHolder();
148 }
149 BiFunction<Integer, Integer, Object> factory = isWeak ?
150 (i1, i2) -> new WeakPoint(i1, i2) :
151 (i1, i2) -> new StrongPoint(i1, i2);
152 boolean allowsNonPlainAccess = (field.getModifiers() & Modifier.VOLATILE) != 0 ||
153 !ValueClass.isNullRestrictedField(field) ||
154 !isWeak;
155 arguments.add(Arguments.of(accessMode, holder, factory, field, allowsNonPlainAccess));
156 }
157 }
158 return arguments;
159 } catch (ReflectiveOperationException ex) {
160 throw new IllegalStateException(ex);
161 }
162 }
163
164 /*
165 * Verify that atomic access modes are not supported on flat fields.
166 */
167 @ParameterizedTest
168 @MethodSource("fieldAccessProvider")
169 public void testFieldAccess(AccessMode accessMode, Object holder, BiFunction<Integer, Integer, Object> factory,
170 Field field, boolean allowsNonPlainAccess) throws Throwable {
171 VarHandle varHandle = MethodHandles.lookup().unreflectVarHandle(field);
172 if (varHandle.isAccessModeSupported(accessMode)) {
173 assertTrue(isPlain(accessMode) || (allowsNonPlainAccess && !isBitwise(accessMode) && !isNumeric(accessMode)));
174 MethodHandle methodHandle = varHandle.toMethodHandle(accessMode);
175 List<Object> arguments = new ArrayList<>();
176 if (holder != null) {
177 arguments.add(holder); // receiver
178 }
179 for (int i = arguments.size(); i < methodHandle.type().parameterCount(); i++) {
180 arguments.add(factory.apply(i, i)); // add extra setter param
181 }
182 methodHandle.invokeWithArguments(arguments.toArray());
183 } else {
184 assertTrue(!allowsNonPlainAccess || isBitwise(accessMode) || isNumeric(accessMode));
185 }
186 }
187
188 private static List<Arguments> arrayAccessProvider() {
189 List<Object[]> arrayObjects = List.of(
190 WeakPoint.makePoints(10, ValueClass::newNullableAtomicArray),
191 WeakPoint.makePoints(10, new WeakPoint(0, 0), ValueClass::newNullRestrictedNonAtomicArray),
192 WeakPoint.makePoints(10, new WeakPoint(0, 0), ValueClass::newNullRestrictedAtomicArray),
193 new WeakPoint[10],
194 StrongPoint.makePoints(10, ValueClass::newNullableAtomicArray),
195 StrongPoint.makePoints(10, new StrongPoint(0, 0), ValueClass::newNullRestrictedNonAtomicArray),
196 StrongPoint.makePoints(10, new StrongPoint(0, 0), ValueClass::newNullRestrictedAtomicArray),
197 new StrongPoint[10]);
198
199 List<Arguments> arguments = new ArrayList<>();
200 for (AccessMode accessMode : AccessMode.values()) {
201 if (accessMode.ordinal() != 2) continue;
202 for (Object[] arrayObject : arrayObjects) {
203 boolean isWeak = arrayObject.getClass().getComponentType().equals(WeakPoint.class);
204 List<Class<?>> arrayTypes = List.of(
205 isWeak ? WeakPoint[].class : StrongPoint[].class, Pointable[].class, Object[].class);
206 for (Class<?> arrayType : arrayTypes) {
207 BiFunction<Integer, Integer, Object> factory = isWeak ?
208 (i1, i2) -> new WeakPoint(i1, i2) :
209 (i1, i2) -> new StrongPoint((short)(int)i1, (short)(int)i2);
210 boolean allowsNonPlainAccess = !ValueClass.isNullRestrictedArray(arrayObject) ||
211 ValueClass.isAtomicArray(arrayObject) ||
212 !isWeak;
213 arguments.add(Arguments.of(accessMode, arrayObject, factory, arrayType, allowsNonPlainAccess));
214 }
215 }
216 }
217 return arguments;
218 }
219
220 /*
221 * Verify that atomic access modes are not supported on flat array instances.
222 */
223 @ParameterizedTest
224 @MethodSource("arrayAccessProvider")
225 public void testArrayAccess(AccessMode accessMode, Object[] arrayObject, BiFunction<Integer, Integer, Object> factory,
226 Class<?> arrayType, boolean allowsNonPlainAccess) throws Throwable {
227 VarHandle varHandle = MethodHandles.arrayElementVarHandle(arrayType);
228 if (varHandle.isAccessModeSupported(accessMode)) {
229 assertTrue(!isBitwise(accessMode) && !isNumeric(accessMode));
230 MethodHandle methodHandle = varHandle.toMethodHandle(accessMode);
231 List<Object> arguments = new ArrayList<>();
232 arguments.add(arrayObject); // array receiver
233 arguments.add(0); // index
234 for (int i = 2; i < methodHandle.type().parameterCount(); i++) {
235 arguments.add(factory.apply(i, i)); // add extra setter param
236 }
237 try {
238 methodHandle.invokeWithArguments(arguments.toArray());
239 } catch (IllegalArgumentException ex) {
240 assertFalse(allowsNonPlainAccess);
241 }
242 } else {
243 assertTrue(isBitwise(accessMode) || isNumeric(accessMode));
244 }
245 }
246
247 boolean isBitwise(AccessMode accessMode) {
248 return switch (accessMode) {
249 case GET_AND_BITWISE_AND, GET_AND_BITWISE_AND_ACQUIRE,
250 GET_AND_BITWISE_AND_RELEASE, GET_AND_BITWISE_OR,
251 GET_AND_BITWISE_OR_ACQUIRE, GET_AND_BITWISE_OR_RELEASE,
252 GET_AND_BITWISE_XOR, GET_AND_BITWISE_XOR_ACQUIRE,
253 GET_AND_BITWISE_XOR_RELEASE -> true;
254 default -> false;
255 };
256 }
257
258 boolean isNumeric(AccessMode accessMode) {
259 return switch (accessMode) {
260 case GET_AND_ADD, GET_AND_ADD_ACQUIRE, GET_AND_ADD_RELEASE -> true;
261 default -> false;
262 };
263 }
264
265 boolean isPlain(AccessMode accessMode) {
266 return switch (accessMode) {
267 case GET, SET -> true;
268 default -> false;
269 };
270 }
271 }