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