1 /*
  2  * Copyright (c) 2025, 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 import jdk.incubator.code.Op;
 25 import jdk.incubator.code.Reflect;
 26 import org.junit.jupiter.api.Assertions;
 27 import org.junit.jupiter.api.Test;
 28 
 29 import java.io.IOException;
 30 import java.lang.classfile.*;
 31 import java.lang.classfile.instruction.ConstantInstruction;
 32 import java.lang.classfile.instruction.InvokeInstruction;
 33 import java.lang.reflect.Method;
 34 import java.nio.file.Files;
 35 import java.nio.file.Path;
 36 import java.util.Optional;
 37 import java.util.Random;
 38 
 39 /*
 40  * @test
 41  * @summary Test that java version check we do in op building methods is working
 42  * @modules jdk.incubator.code
 43  * @run main TestJavaVersionCheckerForMethods
 44  * @run junit/othervm TestJavaVersionCheckerForMethods
 45  */
 46 public class TestJavaVersionCheckerForMethods {
 47 
 48     public static void main(String[] args) throws IOException { // transform $CM classfile
 49         String testClassName = TestJavaVersionCheckerForMethods.class.getName();
 50         Path testClassesDir = Path.of(System.getProperty("test.classes"));
 51         Path innerClassPath = testClassesDir.resolve(testClassName + "$$CM.class");
 52         byte[] newInnerBytes = changeCompileTimeVersion(innerClassPath, Runtime.version().feature() - 1);
 53         Files.write(innerClassPath, newInnerBytes);
 54     }
 55 
 56     @Test
 57     void test() throws ReflectiveOperationException, IOException {
 58         Method m = this.getClass().getDeclaredMethod("max", int.class, int.class);
 59         Assertions.assertThrows(UnsupportedOperationException.class, () -> Op.ofMethod(m));
 60         // invoke Op.ofMethod a second time to make sure its behavior is the same
 61         Assertions.assertThrows(UnsupportedOperationException.class, () -> Op.ofMethod(m));
 62     }
 63 
 64     // change java compile time version that was embedded in the $checkJavaVersion method
 65     static byte[] changeCompileTimeVersion(Path innerClassPath, int newCompileTimeVersion) {
 66         ClassModel inner = null;
 67         try {
 68             inner = ClassFile.of().parse(innerClassPath);
 69         } catch (IOException e) {
 70             Assertions.fail("Inner class holding the code model doesn't exist in " + innerClassPath);
 71         }
 72 
 73         Optional<MethodModel> optional = inner.methods().stream().filter(m -> m.methodName().equalsString("$checkJavaVersion")).findFirst();
 74         Assertions.assertTrue(optional.isPresent(), "Helper method that checks the java version doesn't exist");
 75         MethodModel checkerMethod = optional.get();
 76 
 77         for (MethodModel m : inner.methods()) {
 78             if (m.methodName().stringValue().startsWith("$")) { // not model building method
 79                 continue;
 80             }
 81             Assertions.assertTrue(m.code().get().elementList().getFirst() instanceof InvokeInstruction i &&
 82                             i.method().owner().asSymbol().equals(inner.thisClass().asSymbol()) &&
 83                             i.name().equals(checkerMethod.methodName()) &&
 84                             i.type().equals(checkerMethod.methodType()),
 85                     "model building method doesn't check Java version at the start");
 86         }
 87 
 88         // @@@ we may want to change the compile time version embedded in the error message ?
 89         CodeTransform codeTransform = (codeBuilder, codeElement) -> {
 90             if (codeElement instanceof ConstantInstruction.ArgumentConstantInstruction a) {
 91                 codeBuilder.bipush(newCompileTimeVersion);
 92             } else {
 93                 codeBuilder.with(codeElement);
 94             }
 95         };
 96         ClassTransform classTransform = (classBuilder, classElement) -> {
 97             if (classElement instanceof MethodModel m && m.equals(checkerMethod)) {
 98                 classBuilder.transformMethod(m, MethodTransform.transformingCode(codeTransform));
 99             } else {
100                 classBuilder.with(classElement);
101             }
102         };
103 
104         return ClassFile.of(ClassFile.ConstantPoolSharingOption.NEW_POOL).transformClass(inner, classTransform);
105     }
106 
107     @Reflect
108     static int max(int a, int b) {
109         return Math.max(a, b);
110     }
111 }