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 strictly-initialized final fields
27 * @enablePreview
28 * @library /test/lib
29 * @build ${test.main.class}
30 * @run driver jdk.test.lib.helpers.StrictProcessor StrictInitFinalFields$TestClass
31 * @run junit/othervm --enable-final-field-mutation=ALL-UNNAMED ${test.main.class}
32 * @run junit/othervm --illegal-final-field-mutation=allow ${test.main.class}
33 */
34
35 import java.lang.invoke.MethodHandle;
36 import java.lang.invoke.MethodHandles;
37 import java.lang.invoke.MethodHandles.Lookup;
38 import java.lang.invoke.VarHandle;
39 import java.lang.reflect.Field;
40 import jdk.test.lib.helpers.StrictInit;
41
42 import org.junit.jupiter.api.Test;
43 import static org.junit.jupiter.api.Assertions.*;
44
45 class StrictInitFinalFields {
46
47 /**
48 * Test class with strictly-initialized final fields.
49 */
50 static class TestClass {
51 @StrictInit
52 private static final int DEFAULT_VALUE;
53 static {
54 DEFAULT_VALUE = 100;
55 }
56
57 @StrictInit
58 private final int value;
59
60 TestClass() {
61 this.value = DEFAULT_VALUE;
62 super();
63 }
64
65 TestClass(int i) {
66 this.value = i;
67 super();
68 }
69 }
70
71 /**
72 * Get/set strictly initialized static field.
73 */
74 @Test
75 void testStaticField() throws Throwable {
76 Field field = TestClass.class.getDeclaredField("DEFAULT_VALUE");
77 assertTrue(field.isStrictInit());
78
79 int initialValue = TestClass.DEFAULT_VALUE;
80 int newValue = initialValue + 100;
81
82 // Field.get/set before setAccessible
83 assertEquals(initialValue, field.get(null));
84 assertThrows(IllegalAccessException.class, () -> field.set(null, newValue));
85
86 field.setAccessible(true);
87
88 // Field.get/set after setAccessible
89 assertEquals(initialValue, field.get(null));
90 assertThrows(IllegalAccessException.class, () -> field.set(null, newValue));
91
92 // VarHandle produced from reflected field
93 var lookup = MethodHandles.lookup();
94 VarHandle vh = lookup.unreflectVarHandle(field);
95 assertEquals(initialValue, vh.get());
96 assertThrows(UnsupportedOperationException.class, () -> vh.set(newValue));
97
98 // Method handles produced from reflected field
99 assertEquals(initialValue, (int) lookup.unreflectGetter(field).invokeExact());
100 assertThrows(IllegalAccessException.class, () -> lookup.unreflectSetter(field));
101
102 MethodHandle mh = lookup.findStaticGetter(TestClass.class, "DEFAULT_VALUE", int.class);
103 assertEquals(initialValue, (int) mh.invokeExact());
104 assertThrows(IllegalAccessException.class,
105 () -> lookup.findStaticSetter(TestClass.class, "DEFAULT_VALUE", int.class));
106
107 assertEquals(initialValue, TestClass.DEFAULT_VALUE); // check unchanged
108 }
109
110 /**
111 * Get/set strictly initialized instance field.
112 */
113 @Test
114 void testInstanceField() throws Throwable {
115 Field field = TestClass.class.getDeclaredField("value");
116 assertTrue(field.isStrictInit());
117
118 var obj = new TestClass(150);
119 int initialValue = obj.value;
120 int newValue = initialValue + 100;
121
122 // Field.get/set before setAccessible
123 assertEquals(initialValue, field.get(obj));
124 assertThrows(IllegalAccessException.class, () -> field.set(obj, newValue));
125
126 // Field.get/set after setAccessible
127 field.setAccessible(true);
128 assertEquals(initialValue, field.get(obj));
129 assertThrows(IllegalAccessException.class, () -> field.set(obj, newValue));
130
131 // VarHandle produced from reflected field
132 var lookup = MethodHandles.lookup();
133 VarHandle vh = lookup.unreflectVarHandle(field);
134 assertEquals(initialValue, vh.get(obj));
135 assertThrows(UnsupportedOperationException.class, () -> vh.set(obj, newValue));
136
137 // Method handles produced from reflected field
138 assertEquals(initialValue, (int) lookup.unreflectGetter(field).invokeExact(obj));
139 assertThrows(IllegalAccessException.class, () -> lookup.unreflectSetter(field));
140
141 MethodHandle mh = lookup.findGetter(TestClass.class, "value", int.class);
142 assertEquals(initialValue, (int) mh.invokeExact(obj));
143 assertThrows(IllegalAccessException.class,
144 () -> lookup.findSetter(TestClass.class, "value", int.class));
145
146 assertEquals(initialValue, obj.value); // check unchanged
147 }
148 }