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 }