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 }