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