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 }