1 /*
2 * Copyright (c) 2021, 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 /*
26 * @test
27 * @summary Test reflection and method handle on accessing a field of a null-restricted value class
28 * that may be flattened or non-flattened
29 * @enablePreview
30 * @run junit/othervm NullRestrictedTest
31 * @run junit/othervm -XX:-UseFieldFlattening NullRestrictedTest
32 */
33
34 import java.lang.invoke.MethodHandles;
35 import java.lang.reflect.Field;
36 import java.util.stream.Stream;
37
38 import jdk.internal.value.ValueClass;
39 import jdk.internal.vm.annotation.NullRestricted;
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 import static org.junit.jupiter.api.Assertions.*;
46
47 public class NullRestrictedTest {
48 static value class EmptyValue {
49 public boolean isEmpty() {
50 return true;
51 }
52 }
53
54 static value class Value {
55 Object o;
56 @NullRestricted
57 EmptyValue empty;
58 Value() {
59 this.o = null;
60 this.empty = new EmptyValue();
61 }
62 Value(EmptyValue empty) {
63 this.o = null;
64 this.empty = empty;
65 }
66 }
67
68 static class Mutable {
69 EmptyValue o;
70 @NullRestricted
71 EmptyValue empty;
72 @NullRestricted
73 volatile EmptyValue vempty;
74
75 Mutable() {
76 empty = new EmptyValue();
77 vempty = new EmptyValue();
78 super();
79 }
80 }
81
82 @Test
83 public void emptyValueClass() {
84 EmptyValue e = new EmptyValue();
85 Field[] fields = e.getClass().getDeclaredFields();
86 assertTrue(fields.length == 0);
87 }
88
89 @Test
90 public void testNonNullFieldAssignment() {
91 var npe = assertThrows(NullPointerException.class, () -> new Value(null));
92 System.err.println(npe); // log the exception message
93 }
94
95 static Stream<Arguments> getterCases() {
96 Value v = new Value();
97 EmptyValue emptyValue = new EmptyValue();
98 Mutable m = new Mutable();
99
100 return Stream.of(
101 Arguments.of(Value.class, "o", Object.class, v, null),
102 Arguments.of(Value.class, "empty", EmptyValue.class, v, emptyValue),
103 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null),
104 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue),
105 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue)
106 );
107 };
108
109 @ParameterizedTest
110 @MethodSource("getterCases")
111 public void testGetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable {
112 var f = type.getDeclaredField(name);
113 assertTrue(f.getType() == ftype);
114 var o1 = f.get(obj);
115 assertTrue(expected == o1);
116
117 var getter = MethodHandles.lookup().findGetter(type, name, ftype);
118 var o2 = getter.invoke(obj);
119 assertTrue(expected == o2);
120
121 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype);
122 var o3 = vh.get(obj);
123 assertTrue(expected == o3);
124 }
125
126 static Stream<Arguments> setterCases() {
127 EmptyValue emptyValue = new EmptyValue();
128 Mutable m = new Mutable();
129 return Stream.of(
130 Arguments.of(Mutable.class, "o", EmptyValue.class, m, null),
131 Arguments.of(Mutable.class, "o", EmptyValue.class, m, emptyValue),
132 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, emptyValue),
133 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, emptyValue)
134 );
135 };
136
137 @ParameterizedTest
138 @MethodSource("setterCases")
139 public void testSetter(Class<?> type, String name, Class<?> ftype, Object obj, Object expected) throws Throwable {
140 var f = type.getDeclaredField(name);
141 assertTrue(f.getType() == ftype);
142 f.set(obj, expected);
143 assertTrue(f.get(obj) == expected);
144
145 var setter = MethodHandles.lookup().findSetter(type, name, ftype);
146 setter.invoke(obj, expected);
147 assertTrue(f.get(obj) == expected);
148 }
149
150 @Test
151 public void noWriteAccess() throws ReflectiveOperationException {
152 Value v = new Value();
153 Field f = v.getClass().getDeclaredField("o");
154 assertThrows(IllegalAccessException.class, () -> f.set(v, null));
155 }
156
157 static Stream<Arguments> nullRestrictedFields() {
158 Mutable m = new Mutable();
159 return Stream.of(
160 Arguments.of(Mutable.class, "o", EmptyValue.class, m, false),
161 Arguments.of(Mutable.class, "empty", EmptyValue.class, m, true),
162 Arguments.of(Mutable.class, "vempty", EmptyValue.class, m, true)
163 );
164 };
165
166 @ParameterizedTest
167 @MethodSource("nullRestrictedFields")
168 public void testNullRestrictedField(Class<?> type, String name, Class<?> ftype, Object obj, boolean nullRestricted) throws Throwable {
169 var f = type.getDeclaredField(name);
170 assertTrue(f.getType() == ftype);
171 if (nullRestricted) {
172 assertThrows(NullPointerException.class, () -> f.set(obj, null));
173 } else {
174 f.set(obj, null);
175 assertTrue(f.get(obj) == null);
176 }
177
178 var mh = MethodHandles.lookup().findSetter(type, name, ftype);
179 if (nullRestricted) {
180 assertThrows(NullPointerException.class, () -> mh.invoke(obj, null));
181 } else {
182 mh.invoke(obj, null);
183 assertTrue(f.get(obj) == null);
184 }
185
186 var vh = MethodHandles.lookup().findVarHandle(type, name, ftype);
187 if (nullRestricted) {
188 assertThrows(NullPointerException.class, () -> vh.set(obj, null));
189 } else {
190 vh.set(obj, null);
191 assertTrue(f.get(obj) == null);
192 }
193 }
194 }