1 /*
  2  * Copyright (c) 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  * @test
 26  * @summary Test Field access to a strictly-initialized static field when initializing
 27  *     the field's class.
 28  * @enablePreview
 29  * @library /test/lib
 30  * @build ${test.main.class}
 31  * @run driver jdk.test.lib.helpers.StrictProcessor AccessStrictInitInClinit$TestClass
 32  * @run junit/othervm ${test.main.class}
 33  */
 34 
 35 import java.io.InputStream;
 36 import java.io.IOException;
 37 import java.lang.invoke.MethodHandle;
 38 import java.lang.invoke.MethodHandles;
 39 import java.lang.invoke.VarHandle;
 40 import java.lang.reflect.Field;
 41 import java.util.function.Consumer;
 42 import jdk.test.lib.helpers.StrictInit;
 43 
 44 import org.junit.jupiter.api.function.ThrowingConsumer;
 45 import org.junit.jupiter.params.ParameterizedTest;
 46 import org.junit.jupiter.params.provider.ValueSource;
 47 import static org.junit.jupiter.api.Assertions.*;
 48 
 49 class AccessStrictInitInClinit {
 50 
 51     static ThrowingConsumer<Field> thunk;
 52 
 53     /**
 54      * Test class with a strictly-initialized static field.
 55      */
 56     static class TestClass {
 57         @StrictInit
 58         static Object aStaticField;
 59 
 60         static {
 61             if (thunk != null) {
 62                 try {
 63                     Field f = TestClass.class.getDeclaredField("aStaticField");
 64                     thunk.accept(f);
 65                 } catch (Throwable e) {
 66                     fail(e);
 67                 }
 68             }
 69             aStaticField = new Object();
 70         }
 71 
 72         /**
 73          * The class bytes for this class.
 74          */
 75         private final static LazyConstant<byte[]> CLASS_BYTES = LazyConstant.of(() -> {
 76             String rn = TestClass.class.getName() + ".class";
 77             InputStream in = TestClass.class.getResourceAsStream(rn);
 78             assertNotNull(in);
 79             try (in) {
 80                 return in.readAllBytes();
 81             } catch (IOException ioe) {
 82                 fail(ioe);
 83                 return null;
 84             }
 85         });
 86 
 87         /**
 88          * Define a class with a strictly-initialized static field. Perform the given
 89          * action with the reflected Field before the field has been initialized.
 90          */
 91         static void executeBeforeFieldSet(ThrowingConsumer<Field> consumer) {
 92             thunk = consumer;
 93             try {
 94                 var _ = MethodHandles.lookup().defineHiddenClass(CLASS_BYTES.get(), true);
 95             } catch (Throwable e) {
 96                 fail(e);
 97             } finally {
 98                 thunk = null;
 99             }
100         }
101     }
102 
103     /**
104      * Test Field.set/get to access field when initializing the field's class.
105      */
106     @ParameterizedTest
107     @ValueSource(booleans = { false, true })
108     void testFieldSetGet(boolean accessible) throws Exception {
109         TestClass.executeBeforeFieldSet(f -> {
110             assertTrue(f.isStrictInit());
111             f.setAccessible(accessible);
112 
113             // Field.get before initialized
114             assertThrows(IllegalStateException.class, () -> f.get(null));
115 
116             // initialize with Field.set
117             Object value = new Object();
118             f.set(null, value);
119             assertSame(value, f.get(null));
120         });
121     }
122 
123     /**
124      * Test setter/getter method handles produced from a Field to access field when
125      * initializing the field's class.
126      */
127     @ParameterizedTest
128     @ValueSource(booleans = { false, true })
129     void testUnreflectSetterGetter(boolean accessible) {
130         TestClass.executeBeforeFieldSet(f -> {
131             assertTrue(f.isStrictInit());
132             f.setAccessible(accessible);
133             MethodHandle setter = MethodHandles.lookup().unreflectSetter(f);
134             MethodHandle getter = MethodHandles.lookup().unreflectGetter(f);
135 
136             // invoke MH getter before initialized
137             assertThrows(IllegalStateException.class, () -> {
138                 var _ = (Object) getter.invokeExact();
139             });
140 
141             // initialize with MH setter
142             Object value = new Object();
143             setter.invokeExact(value);
144             assertSame(value, f.get(null));
145             assertSame(value, (Object) getter.invokeExact());
146         });
147     }
148 
149     /**
150      * Test VarHandle produced from a Field to access field when initializing the field's class.
151      */
152     @ParameterizedTest
153     @ValueSource(booleans = { false, true })
154     void testUnreflectVarHandle(boolean accessible) {
155         TestClass.executeBeforeFieldSet(f -> {
156             assertTrue(f.isStrictInit());
157             f.setAccessible(accessible);
158             VarHandle vh = MethodHandles.lookup().unreflectVarHandle(f);
159 
160             // use VH get before initialized
161             assertThrows(IllegalStateException.class, () -> vh.get());
162             assertThrows(IllegalStateException.class, () -> vh.getAndSet(new Object()));
163 
164             // initialize with VH set
165             Object value1 = new Object();
166             Object value2 = new Object();
167             vh.set(value1);
168             assertSame(value1, f.get(null));
169             assertSame(value1, vh.get());
170             assertSame(value1, vh.getAndSet(value2));
171             assertSame(value2, f.get(null));
172             assertSame(value2, vh.get());
173         });
174     }
175 }