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 }