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::isStrictInit on fields that have ACC_STRICT_INIT set or not set
27 * in a selection of class file versions. ACC_STRICT_INIT is considered not set when
28 * the class file version is not 71.65535.
29 * @modules java.base/jdk.internal.misc
30 * @run junit/othervm ${test.main.class}
31 * @run junit/othervm --enable-preview ${test.main.class}
32 */
33
34 import java.lang.constant.ClassDesc;
35 import java.lang.classfile.ClassFile;
36 import java.lang.invoke.MethodHandles;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.Modifier;
39 import java.util.ArrayList;
40 import java.util.stream.Stream;
41 import jdk.internal.misc.PreviewFeatures;
42 import static java.lang.classfile.ClassFile.ACC_FINAL;
43 import static java.lang.classfile.ClassFile.ACC_IDENTITY;
44 import static java.lang.classfile.ClassFile.ACC_STATIC;
45 import static java.lang.classfile.ClassFile.ACC_STRICT_INIT;
46 import static java.lang.classfile.ClassFile.JAVA_8_VERSION;
47 import static java.lang.classfile.ClassFile.JAVA_25_VERSION;
48 import static java.lang.classfile.ClassFile.PREVIEW_MINOR_VERSION;
49 import static java.lang.constant.ConstantDescs.CD_Object;
50 import static java.lang.constant.ConstantDescs.CD_int;
51 import static java.lang.constant.ConstantDescs.CLASS_INIT_NAME;
52 import static java.lang.constant.ConstantDescs.INIT_NAME;
53 import static java.lang.constant.ConstantDescs.MTD_void;
54
55 import org.junit.jupiter.params.ParameterizedTest;
56 import org.junit.jupiter.params.provider.Arguments;
57 import org.junit.jupiter.params.provider.MethodSource;
58 import static org.junit.jupiter.api.Assertions.*;
59
60 class IsStrictInitTest {
61
62 /**
63 * Class file versions to test and whether to set the ACC_STRICT_INIT flag on the fields.
64 */
65 static Stream<Arguments> testCases() {
66 var cases = new ArrayList<Arguments>();
67 cases.add(Arguments.arguments(JAVA_8_VERSION, 0, false));
68 cases.add(Arguments.arguments(JAVA_8_VERSION, 0, true));
69 cases.add(Arguments.arguments(JAVA_25_VERSION, 0, false));
70 cases.add(Arguments.arguments(JAVA_25_VERSION, 0, true));
71 cases.add(Arguments.arguments(ClassFile.latestMajorVersion(), 0, false));
72 cases.add(Arguments.arguments(ClassFile.latestMajorVersion(), 0, true));
73 if (PreviewFeatures.isEnabled()) {
74 cases.add(Arguments.arguments(ClassFile.latestMajorVersion(), PREVIEW_MINOR_VERSION, false));
75 cases.add(Arguments.arguments(ClassFile.latestMajorVersion(), PREVIEW_MINOR_VERSION, true));
76 }
77 return cases.stream();
78 }
79
80 @ParameterizedTest
81 @MethodSource("testCases")
82 void testIsStrictInit(int majorVersion, int minorVersion, boolean strictInit) throws Exception {
83 byte[] classBytes = buildClass(majorVersion, minorVersion, strictInit);
84 Class<?> clazz = MethodHandles.lookup()
85 .defineHiddenClass(classBytes, false)
86 .lookupClass();
87
88 // ACC_STRICT_INIT flag should be ignored when not 71.65535
89 boolean expectStrictInit = strictInit && (minorVersion == PREVIEW_MINOR_VERSION);
90
91 // static field
92 Field f1 = clazz.getDeclaredField("aStaticField");
93 System.err.format("%s, accessFlags = %s%n", f1, f1.accessFlags());
94 assertTrue(Modifier.isStatic(f1.getModifiers()));
95 assertEquals(expectStrictInit, f1.isStrictInit());
96
97 // instance field
98 Field f2 = clazz.getDeclaredField("aField");
99 System.err.format("%s, accessFlags = %s%n", f2, f2.accessFlags());
100 assertFalse(Modifier.isStatic(f2.getModifiers()));
101 assertEquals(expectStrictInit, f2.isStrictInit());
102 }
103
104 /**
105 * Returns the class bytes for a class with a final static and final instance field.
106 * @param majorVersion major class file version
107 * @param minorVersion minor class file version
108 * @param strictInit true to set the ACC_STRICT_INIT flag on the fields
109 */
110 @SuppressWarnings("preview")
111 private byte[] buildClass(int majorVersion, int minorVersion, boolean strictInit) {
112 ClassDesc generateDesc = ClassDesc.of("TestClass");
113 int flags = ACC_FINAL | (strictInit ? ACC_STRICT_INIT : 0);
114 return ClassFile.of().build(generateDesc, builder -> builder
115 .withVersion(majorVersion, minorVersion)
116 .withFlags(ACC_IDENTITY)
117 .withField("aField", CD_int, flags)
118 .withField("aStaticField", CD_int, ACC_STATIC | flags)
119 .withMethodBody(INIT_NAME, MTD_void, 0, cb -> cb
120 .aload(0)
121 .bipush(42)
122 .putfield(generateDesc, "aField", CD_int)
123 .aload(0)
124 .invokespecial(CD_Object, INIT_NAME, MTD_void)
125 .return_())
126 .withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, cb -> cb
127 .bipush(73)
128 .putstatic(generateDesc, "aStaticField", CD_int)
129 .return_()));
130 }
131 }