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 }