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 * @bug 8291065
27 * @summary Checks interaction of static field VarHandle with class
28 * initialization mechanism.
29 * @enablePreview
30 * @library java.base /test/lib
31 * @build java.base/java.lang.invoke.*
32 * LazyInitializingTest ${test.main.class}
33 * @run driver jdk.test.lib.helpers.StrictProcessor StrictInitializingSample StrictStaticFinalHolder
34 * @run junit ${test.main.class}
35 * @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true ${test.main.class}
36 * @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false ${test.main.class}
37 * @run junit/othervm -Djava.lang.invoke.VarHandle.VAR_HANDLE_IDENTITY_ADAPT=true
38 * -Djava.lang.invoke.VarHandle.VAR_HANDLE_GUARDS=false ${test.main.class}
39 */
40
41 import java.io.IOException;
42 import java.lang.constant.ConstantDescs;
43 import java.lang.invoke.LookupHelper;
44 import java.lang.invoke.MethodHandle;
45 import java.lang.invoke.MethodHandles;
46 import java.lang.invoke.VarHandle;
47 import java.util.Objects;
48
49 import jdk.test.lib.helpers.StrictInit;
50 import org.junit.jupiter.api.Test;
51 import org.junit.jupiter.params.ParameterizedTest;
52 import org.junit.jupiter.params.provider.MethodSource;
53
54 import static org.junit.jupiter.api.Assertions.*;
55
56 public class StrictInitializingTest {
57
58 private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
59
60 @Test
61 public void testOperationsDuringInit() throws Throwable {
62 VarHandle[] handle = new VarHandle[1];
63 boolean[] executed = {false};
64 var ci = createSampleClass(new LazyInitializingTest.SampleData(() -> {
65 assertThrows(IllegalStateException.class, () -> {
66 int _ = (int) handle[0].get();
67 }, "get before write");
68 assertThrows(IllegalStateException.class, () -> {
69 boolean _ = (boolean) handle[0].compareAndSet(7, 3);
70 }, "compareAndSet before write");
71 assertThrows(IllegalStateException.class, () -> {
72 int _ = (int) handle[0].getAndAdd(3);
73 }, "getAndAdd before write");
74 handle[0].set(12);
75 assertEquals(12, (int) handle[0].get());
76 assertEquals(12, (int) handle[0].getAndAddAcquire(3));
77 assertEquals(15, (int) handle[0].getVolatile());
78 executed[0] = true;
79 }, 24));
80 handle[0] = ci.vh();
81
82 ci.definingLookup().ensureInitialized(ci.definingLookup().lookupClass());
83 assertTrue(executed[0]);
84 }
85
86 @Test
87 public void testOperationsOnMethodHandleDuringInit() throws Throwable {
88 MethodHandle[] handle = new MethodHandle[7];
89 boolean[] executed = {false};
90 var ci = createSampleClass(new LazyInitializingTest.SampleData(() -> {
91 assertThrows(IllegalStateException.class, () -> {
92 int _ = (int) handle[0].invoke();
93 }, "get before write");
94 assertThrows(IllegalStateException.class, () -> {
95 boolean _ = (boolean) handle[1].invoke(7, 3);
96 }, "compareAndSet before write");
97 assertThrows(IllegalStateException.class, () -> {
98 int _ = (int) handle[2].invoke(3);
99 }, "getAndAdd before write");
100 assertDoesNotThrow(() -> {
101 handle[3].invokeExact(12);
102 assertEquals(12, (int) handle[4].invokeExact());
103 assertEquals(12, (int) handle[5].invokeExact((int) 3));
104 assertEquals(15, (int) handle[6].invokeExact());
105 });
106 executed[0] = true;
107 }, 24));
108 var vh = ci.vh();
109 handle[0] = vh.toMethodHandle(VarHandle.AccessMode.GET);
110 handle[1] = vh.toMethodHandle(VarHandle.AccessMode.COMPARE_AND_SET);
111 handle[2] = vh.toMethodHandle(VarHandle.AccessMode.GET_AND_ADD);
112 handle[3] = vh.toMethodHandle(VarHandle.AccessMode.SET);
113 handle[4] = vh.toMethodHandle(VarHandle.AccessMode.GET);
114 handle[5] = vh.toMethodHandle(VarHandle.AccessMode.GET_AND_ADD_ACQUIRE);
115 handle[6] = vh.toMethodHandle(VarHandle.AccessMode.GET_VOLATILE);
116
117 ci.definingLookup().ensureInitialized(ci.definingLookup().lookupClass());
118 assertTrue(executed[0]);
119 }
120
121 private static MethodHandles.Lookup[] lookups() {
122 return new MethodHandles.Lookup[] {
123 LookupHelper.IMPL_LOOKUP,
124 LOOKUP,
125 };
126 }
127
128 @ParameterizedTest
129 @MethodSource("lookups")
130 public void testSetAccessOnStaticFinal(MethodHandles.Lookup lookup) throws Throwable {
131 var strictStaticFinal = lookup.findStaticVarHandle(StrictStaticFinalHolder.class, "strictStaticFinal", Object.class);
132 assertFalse(strictStaticFinal.isAccessModeSupported(VarHandle.AccessMode.SET));
133 var trustedStaticFinal = lookup.findStaticVarHandle(StrictStaticFinalHolder.class, "trustedStaticFinal", Object.class);
134 assertFalse(trustedStaticFinal.isAccessModeSupported(VarHandle.AccessMode.SET));
135 var strictInstanceFinal = lookup.findVarHandle(StrictStaticFinalHolder.class, "strictFinal", Object.class);
136 assertFalse(strictInstanceFinal.isAccessModeSupported(VarHandle.AccessMode.SET));
137 }
138
139 static LazyInitializingTest.ClassInfo createSampleClass(LazyInitializingTest.SampleData sampleData) {
140 try {
141 var lookup = LOOKUP.defineHiddenClassWithClassData(sampleClassBytes(), sampleData, false);
142 var vh = lookup.findStaticVarHandle(lookup.lookupClass(), "f", int.class);
143 return new LazyInitializingTest.ClassInfo(lookup, vh);
144 } catch (IllegalAccessException | NoSuchFieldException ex) {
145 throw new AssertionError(ex);
146 }
147 }
148
149 private static byte[] sampleClassBytes;
150
151 private static byte[] sampleClassBytes() {
152 var bytes = sampleClassBytes;
153 if (bytes != null)
154 return bytes;
155
156 try (var in = StrictInitializingTest.class.getResourceAsStream("StrictInitializingSample.class")) {
157 if (in == null)
158 throw new AssertionError("class file not found");
159 return sampleClassBytes = in.readAllBytes();
160 } catch (IOException ex) {
161 throw new AssertionError(ex);
162 }
163 }
164 }
165
166 // This is used as a template class, whose bytes are used to define
167 // hidden classes instead
168 class StrictInitializingSample {
169 @StrictInit
170 static int f;
171
172 static {
173 try {
174 var data = MethodHandles.classData(MethodHandles.lookup(), ConstantDescs.DEFAULT_NAME,
175 LazyInitializingTest.SampleData.class);
176 Objects.requireNonNull(data);
177
178 data.callback().run();
179 f = data.initialValue();
180 } catch (IllegalAccessException e) {
181 throw new ExceptionInInitializerError(e);
182 }
183 }
184 }
185
186 final class StrictStaticFinalHolder {
187 @StrictInit
188 static final Object strictStaticFinal = 5;
189 static final Object trustedStaticFinal = new Object();
190
191 @StrictInit
192 final Object strictFinal;
193
194 StrictStaticFinalHolder(Object strictFinal) {
195 this.strictFinal = strictFinal;
196 super();
197 }
198 }