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 }