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 }