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