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 }