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.NullRestrictedCheckedType;
 29 import jdk.internal.value.ValueClass;
 30 import jdk.internal.vm.annotation.ImplicitlyConstructible;
 31 import jdk.internal.vm.annotation.LooselyConsistentValue;
 32 import jdk.internal.vm.annotation.NullRestricted;
 33 import org.junit.jupiter.params.provider.Arguments;
 34 import org.junit.jupiter.params.provider.MethodSource;
 35 import org.junit.jupiter.params.ParameterizedTest;
 36 
 37 import java.lang.invoke.MethodHandle;
 38 import java.lang.invoke.MethodHandles;
 39 import java.lang.invoke.VarHandle;
 40 import java.lang.invoke.VarHandle.AccessMode;
 41 import java.lang.reflect.Field;
 42 import java.lang.reflect.Modifier;
 43 import java.util.ArrayList;
 44 import java.util.List;
 45 import java.util.function.BiFunction;
 46 
 47 // TODO: 8353180: Remove requires != Xcomp
 48 
 49 /*
 50  * @test
 51  * @requires vm.compMode != "Xcomp"
 52  * @summary Test atomic access modes on var handles for flattened values
 53  * @enablePreview
 54  * @modules java.base/jdk.internal.value java.base/jdk.internal.vm.annotation
 55  * @run junit/othervm -XX:-UseArrayFlattening -XX:-UseNullableValueFlattening FlatVarHandleTest
 56  * @run junit/othervm -XX:+UseArrayFlattening -XX:+UseNullableValueFlattening FlatVarHandleTest
 57  */
 58 public class FlatVarHandleTest {
 59 
 60     interface Pointable { }
 61 
 62     @ImplicitlyConstructible
 63     @LooselyConsistentValue
 64     static value class WeakPoint implements Pointable {
 65         short x,y;
 66         WeakPoint(int i, int j) { x = (short)i; y = (short)j; }
 67 
 68         static WeakPoint[] makePoints(int len, BiFunction<Class<?>, Integer, Object[]> arrayFactory) {
 69             WeakPoint[] array = (WeakPoint[])arrayFactory.apply(WeakPoint.class, len);
 70             for (int i = 0; i < len; ++i) {
 71                 array[i] = new WeakPoint(i, i);
 72             }
 73             return array;
 74         }
 75     }
 76 
 77     static class WeakPointHolder {
 78         WeakPoint p_i = new WeakPoint(0, 0);
 79         static WeakPoint p_s = new WeakPoint(0, 0);
 80         @NullRestricted
 81         WeakPoint p_i_nr = new WeakPoint(0, 0);
 82         @NullRestricted
 83         static WeakPoint p_s_nr = new WeakPoint(0, 0);
 84     }
 85 
 86     @ImplicitlyConstructible
 87     static value class StrongPoint implements Pointable {
 88         short x,y;
 89         StrongPoint(int i, int j) { x = (short)i; y = (short)j; }
 90 
 91         static StrongPoint[] makePoints(int len, BiFunction<Class<?>, Integer, Object[]> arrayFactory) {
 92             StrongPoint[] array = (StrongPoint[])arrayFactory.apply(StrongPoint.class, len);
 93             for (int i = 0; i < len; ++i) {
 94                 array[i] = new StrongPoint(i, i);
 95             }
 96             return array;
 97         }
 98     }
 99 
100     static class StrongPointHolder {
101         StrongPoint p_i = new StrongPoint(0, 0);
102         static StrongPoint p_s = new StrongPoint(0, 0);
103     }
104 
105     private static List<Arguments> fieldAccessProvider() {
106         try {
107             List<Field> fields = List.of(
108                     WeakPointHolder.class.getDeclaredField("p_s"),
109                     WeakPointHolder.class.getDeclaredField("p_i"),
110                     WeakPointHolder.class.getDeclaredField("p_s_nr"),
111                     WeakPointHolder.class.getDeclaredField("p_i_nr"),
112                     StrongPointHolder.class.getDeclaredField("p_s"),
113                     StrongPointHolder.class.getDeclaredField("p_i"));
114             List<Arguments> arguments = new ArrayList<>();
115             for (AccessMode accessMode : AccessMode.values()) {
116                 for (Field field : fields) {
117                     boolean isStatic = (field.getModifiers() & Modifier.STATIC) != 0;
118                     boolean isWeak = field.getDeclaringClass().equals(WeakPointHolder.class);
119                     Object holder = null;
120                     if (!isStatic) {
121                         holder = isWeak ? new WeakPointHolder() : new StrongPointHolder();
122                     }
123                     BiFunction<Integer, Integer, Object> factory = isWeak ?
124                             (i1, i2) -> new WeakPoint(i1, i2) :
125                             (i1, i2) -> new StrongPoint(i1, i2);
126                     boolean allowsNonPlainAccess = (field.getModifiers() & Modifier.VOLATILE) != 0 ||
127                             !(ValueClass.checkedType(field) instanceof NullRestrictedCheckedType) ||
128                             !isWeak;
129                     arguments.add(Arguments.of(accessMode, holder, factory, field, allowsNonPlainAccess));
130                 }
131             }
132             return arguments;
133         } catch (ReflectiveOperationException ex) {
134             throw new IllegalStateException(ex);
135         }
136     }
137 
138     /*
139      * Verify that atomic access modes are not supported on flat fields.
140      */
141     @ParameterizedTest
142     @MethodSource("fieldAccessProvider")
143     public void testFieldAccess(AccessMode accessMode, Object holder, BiFunction<Integer, Integer, Object> factory,
144                                 Field field, boolean allowsNonPlainAccess) throws Throwable {
145         VarHandle varHandle = MethodHandles.lookup().unreflectVarHandle(field);
146         if (varHandle.isAccessModeSupported(accessMode)) {
147             assertTrue(isPlain(accessMode) || (allowsNonPlainAccess && !isBitwise(accessMode) && !isNumeric(accessMode)));
148             MethodHandle methodHandle = varHandle.toMethodHandle(accessMode);
149             List<Object> arguments = new ArrayList<>();
150             if (holder != null) {
151                 arguments.add(holder); // receiver
152             }
153             for (int i = arguments.size(); i < methodHandle.type().parameterCount(); i++) {
154                 arguments.add(factory.apply(i, i)); // add extra setter param
155             }
156             methodHandle.invokeWithArguments(arguments.toArray());
157         } else {
158             assertTrue(!allowsNonPlainAccess || isBitwise(accessMode) || isNumeric(accessMode));
159         }
160     }
161 
162     private static List<Arguments> arrayAccessProvider() {
163         List<Object[]> arrayObjects = List.of(
164                 WeakPoint.makePoints(10, ValueClass::newNullableAtomicArray),
165                 WeakPoint.makePoints(10, ValueClass::newNullRestrictedArray),
166                 WeakPoint.makePoints(10, ValueClass::newNullRestrictedAtomicArray),
167                 new WeakPoint[10],
168                 StrongPoint.makePoints(10, ValueClass::newNullableAtomicArray),
169                 StrongPoint.makePoints(10, ValueClass::newNullRestrictedArray),
170                 StrongPoint.makePoints(10, ValueClass::newNullRestrictedAtomicArray),
171                 new StrongPoint[10]);
172 
173         List<Arguments> arguments = new ArrayList<>();
174         for (AccessMode accessMode : AccessMode.values()) {
175             if (accessMode.ordinal() != 2) continue;
176             for (Object[] arrayObject : arrayObjects) {
177                 boolean isWeak = arrayObject.getClass().getComponentType().equals(WeakPoint.class);
178                 List<Class<?>> arrayTypes = List.of(
179                         isWeak ? WeakPoint[].class : StrongPoint[].class, Pointable[].class, Object[].class);
180                 for (Class<?> arrayType : arrayTypes) {
181                     BiFunction<Integer, Integer, Object> factory = isWeak ?
182                             (i1, i2) -> new WeakPoint(i1, i2) :
183                             (i1, i2) -> new StrongPoint((short)(int)i1, (short)(int)i2);
184                     boolean allowsNonPlainAccess = !ValueClass.isNullRestrictedArray(arrayObject) ||
185                             ValueClass.isAtomicArray(arrayObject) ||
186                             !isWeak;
187                     arguments.add(Arguments.of(accessMode, arrayObject, factory, arrayType, allowsNonPlainAccess));
188                 }
189             }
190         }
191         return arguments;
192     }
193 
194     /*
195      * Verify that atomic access modes are not supported on flat array instances.
196      */
197     @ParameterizedTest
198     @MethodSource("arrayAccessProvider")
199     public void testArrayAccess(AccessMode accessMode, Object[] arrayObject, BiFunction<Integer, Integer, Object> factory,
200                                 Class<?> arrayType, boolean allowsNonPlainAccess) throws Throwable {
201         VarHandle varHandle = MethodHandles.arrayElementVarHandle(arrayType);
202         if (varHandle.isAccessModeSupported(accessMode)) {
203             assertTrue(!isBitwise(accessMode) && !isNumeric(accessMode));
204             MethodHandle methodHandle = varHandle.toMethodHandle(accessMode);
205             List<Object> arguments = new ArrayList<>();
206             arguments.add(arrayObject); // array receiver
207             arguments.add(0); // index
208             for (int i = 2; i < methodHandle.type().parameterCount(); i++) {
209                 arguments.add(factory.apply(i, i)); // add extra setter param
210             }
211             try {
212                 methodHandle.invokeWithArguments(arguments.toArray());
213             } catch (IllegalArgumentException ex) {
214                 assertFalse(allowsNonPlainAccess);
215             }
216         } else {
217             assertTrue(isBitwise(accessMode) || isNumeric(accessMode));
218         }
219     }
220 
221     boolean isBitwise(AccessMode accessMode) {
222         return switch (accessMode) {
223             case GET_AND_BITWISE_AND, GET_AND_BITWISE_AND_ACQUIRE,
224                  GET_AND_BITWISE_AND_RELEASE, GET_AND_BITWISE_OR,
225                  GET_AND_BITWISE_OR_ACQUIRE, GET_AND_BITWISE_OR_RELEASE,
226                  GET_AND_BITWISE_XOR, GET_AND_BITWISE_XOR_ACQUIRE,
227                  GET_AND_BITWISE_XOR_RELEASE -> true;
228             default -> false;
229         };
230     }
231 
232     boolean isNumeric(AccessMode accessMode) {
233         return switch (accessMode) {
234             case GET_AND_ADD, GET_AND_ADD_ACQUIRE, GET_AND_ADD_RELEASE -> true;
235             default -> false;
236         };
237     }
238 
239     boolean isPlain(AccessMode accessMode) {
240         return switch (accessMode) {
241             case GET, SET -> true;
242             default -> false;
243         };
244     }
245 }