/* * Copyright 2015 SAP AG. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* * @test * @bug 8141551 * @summary C2 can not handle returns with inccompatible interface arrays * @modules java.base/jdk.internal.org.objectweb.asm * java.base/sun.misc * @library /testlibrary /../../test/lib * @build sun.hotspot.WhiteBox * @run main ClassFileInstaller sun.hotspot.WhiteBox * sun.hotspot.WhiteBox$WhiteBoxPermission * @run main/othervm * -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI * -Xbatch * -XX:CompileThreshold=1 * -XX:-TieredCompilation * -XX:CICompilerCount=1 * -XX:+PrintCompilation * -XX:+PrintInlining * -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run * -XX:CompileCommand=dontinline,TestMeetIncompatibleInterfaceArrays$Helper.createI2* * -XX:CompileCommand=quiet * TestMeetIncompatibleInterfaceArrays 0 * @run main/othervm * -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI * -Xbatch * -XX:CompileThreshold=1 * -XX:-TieredCompilation * -XX:CICompilerCount=1 * -XX:+PrintCompilation * -XX:+PrintInlining * -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run * -XX:CompileCommand=inline,TestMeetIncompatibleInterfaceArrays$Helper.createI2* * -XX:CompileCommand=quiet * TestMeetIncompatibleInterfaceArrays 1 * @run main/othervm * -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions * -XX:+WhiteBoxAPI * -Xbatch * -XX:CompileThreshold=1 * -XX:Tier0InvokeNotifyFreqLog=0 -XX:Tier2InvokeNotifyFreqLog=0 -XX:Tier3InvokeNotifyFreqLog=0 -XX:Tier23InlineeNotifyFreqLog=0 * -XX:Tier3InvocationThreshold=2 -XX:Tier3MinInvocationThreshold=2 -XX:Tier3CompileThreshold=2 * -XX:Tier4InvocationThreshold=1 -XX:Tier4MinInvocationThreshold=1 -XX:Tier4CompileThreshold=1 * -XX:+TieredCompilation * -XX:CICompilerCount=2 * -XX:+PrintCompilation * -XX:+PrintInlining * -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run * -XX:CompileCommand=compileonly,TestMeetIncompatibleInterfaceArrays$Helper.createI2* * -XX:CompileCommand=inline,TestMeetIncompatibleInterfaceArrays$Helper.createI2* * -XX:CompileCommand=quiet * TestMeetIncompatibleInterfaceArrays 2 * * @author volker.simonis@gmail.com */ import java.io.FileOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import static jdk.internal.org.objectweb.asm.Opcodes.*; import sun.hotspot.WhiteBox; public class TestMeetIncompatibleInterfaceArrays extends ClassLoader { private static final WhiteBox WB = WhiteBox.getWhiteBox(); public static interface I1 { public String getName(); } public static interface I2 { public String getName(); } public static class I2C implements I2 { public String getName() { return "I2";} } public static class I21C implements I2, I1 { public String getName() { return "I2 and I1";} } public static class Helper { public static I2 createI2Array0() { return new I2C(); } public static I2[] createI2Array1() { return new I2C[] { new I2C() }; } public static I2[][] createI2Array2() { return new I2C[][] { new I2C[] { new I2C() } }; } public static I2[][][] createI2Array3() { return new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } }; } public static I2[][][][] createI2Array4() { return new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } }; } public static I2[][][][][] createI2Array5() { return new I2C[][][][][] { new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } } }; } public static I2 createI21Array0() { return new I21C(); } public static I2[] createI21Array1() { return new I21C[] { new I21C() }; } public static I2[][] createI21Array2() { return new I21C[][] { new I21C[] { new I21C() } }; } public static I2[][][] createI21Array3() { return new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } }; } public static I2[][][][] createI21Array4() { return new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } }; } public static I2[][][][][] createI21Array5() { return new I21C[][][][][] { new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } } }; } } // Location for the generated class files public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator; /* * With 'good == false' this helper method creates the following classes * (using the nested 'Helper' class and the nested interfaces 'I1' and 'I2'). * For brevity I omit the enclosing class 'TestMeetIncompatibleInterfaceArrays' in the * following examples: * * public class MeetIncompatibleInterfaceArrays0ASM { * public static I1 run() { * return Helper.createI2Array0(); // returns I2 * } * public static void test() { * I1 i1 = run(); * System.out.println(i1.getName()); * } * } * public class MeetIncompatibleInterfaceArrays1ASM { * public static I1[] run() { * return Helper.createI2Array1(); // returns I2[] * } * public static void test() { * I1[] i1 = run(); * System.out.println(i1[0].getName()); * } * } * ... * // MeetIncompatibleInterfaceArrays4ASM is special because it creates * // an illegal class which will be rejected by the verifier. * public class MeetIncompatibleInterfaceArrays4ASM { * public static I1[][][][] run() { * return Helper.createI2Array3(); // returns I1[][][] which gives a verifier error because return expects I1[][][][] * } * public static void test() { * I1[][][][][] i1 = run(); * System.out.println(i1[0][0][0][0][0].getName()); * } * ... * public class MeetIncompatibleInterfaceArrays5ASM { * public static I1[][][][][] run() { * return Helper.createI2Array5(); // returns I2[][][][][] * } * public static void test() { * I1[][][][][] i1 = run(); * System.out.println(i1[0][0][0][0][0].getName()); * } * } * * Notice that this is not legal Java code. We would have to use a cast in "run()" to make it legal: * * public static I1[] run() { * return (I1[])Helper.createI2Array1(); // returns I2[] * } * * But in pure bytecode, the "run()" methods are perfectly legal: * * public static I1[] run(); * Code: * 0: invokestatic #16 // Method Helper.createI2Array1:()[LI2; * 3: areturn * * The "test()" method calls the "getName()" function from I1 on the objects returned by "run()". * This will epectedly fail with an "IncompatibleClassChangeError" because the objects returned * by "run()" (and by createI2Array()) are actually of type "I2C" and only implement "I2" but not "I1". * * * With 'good == true' this helper method will create the following classes: * * public class MeetIncompatibleInterfaceArraysGood0ASM { * public static I1 run() { * return Helper.createI21Array0(); // returns I2 * } * public static void test() { * I1 i1 = run(); * System.out.println(i1.getName()); * } * } * * Calling "test()" on these objects will succeed and output "I2 and I1" because now the "run()" * method calls "createI21Array()" which actually return an object (or an array of objects) of * type "I21C" which implements both "I2" and "I1". * * Notice that at the bytecode level, the code for the "run()" and "test()" methods in * "MeetIncompatibleInterfaceArraysASM" and "MeetIncompatibleInterfaceArraysGoodASM" look exactly * the same. I.e. the verifier has no chance to verify if the I2 object returned by "createI1Array()" * or "createI21Array()" implements "I1" or not. That's actually the reason why both versions of * generated classes are legal from a verifier point of view. * */ static void generateTestClass(int dim, boolean good) throws Exception { String baseClassName = "MeetIncompatibleInterfaceArrays"; if (good) baseClassName += "Good"; String createName = "createI2" + (good ? "1" : "") + "Array"; String a = ""; for (int i = 0; i < dim; i++) a += "["; ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); cw.visit(V1_8, ACC_PUBLIC, baseClassName + dim + "ASM", null, "java/lang/Object", null); MethodVisitor constr = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); constr.visitCode(); constr.visitVarInsn(ALOAD, 0); constr.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); constr.visitInsn(RETURN); constr.visitMaxs(0, 0); constr.visitEnd(); MethodVisitor run = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "run", "()" + a + "LTestMeetIncompatibleInterfaceArrays$I1;", null, null); run.visitCode(); if (dim == 4) { run.visitMethodInsn(INVOKESTATIC, "TestMeetIncompatibleInterfaceArrays$Helper", createName + 3, "()" + "[[[" + "LTestMeetIncompatibleInterfaceArrays$I2;", false); } else { run.visitMethodInsn(INVOKESTATIC, "TestMeetIncompatibleInterfaceArrays$Helper", createName + dim, "()" + a + "LTestMeetIncompatibleInterfaceArrays$I2;", false); } run.visitInsn(ARETURN); run.visitMaxs(0, 0); run.visitEnd(); MethodVisitor test = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null); test.visitCode(); test.visitMethodInsn(INVOKESTATIC, baseClassName + dim + "ASM", "run", "()" + a + "LTestMeetIncompatibleInterfaceArrays$I1;", false); test.visitVarInsn(ASTORE, 0); if (dim > 0) { test.visitVarInsn(ALOAD, 0); for (int i = 1; i <= dim; i++) { test.visitInsn(ICONST_0); test.visitInsn(AALOAD); } test.visitVarInsn(ASTORE, 1); } test.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); test.visitVarInsn(ALOAD, dim > 0 ? 1 : 0); test.visitMethodInsn(INVOKEINTERFACE, "TestMeetIncompatibleInterfaceArrays$I1", "getName", "()Ljava/lang/String;", true); test.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false); test.visitInsn(RETURN); test.visitMaxs(0, 0); test.visitEnd(); // Get the bytes of the class.. byte[] b = cw.toByteArray(); // ..and write them into a class file (for debugging) FileOutputStream fos = new FileOutputStream(PATH + baseClassName + dim + "ASM.class"); fos.write(b); fos.close(); } public static String[][] tier = { { "interpreted", "C2 (tier 4) without inlining", "C2 (tier4) without inlining" }, { "interpreted", "C2 (tier 4) with inlining", "C2 (tier4) with inlining" }, { "interpreted", "C1 (tier 3) with inlining", "C2 (tier4) with inlining" } }; public static void main(String[] args) throws Exception { final int pass = Integer.parseInt(args.length > 0 ? args[0] : "0"); // Load and initialize some classes required for compilation Class.forName("TestMeetIncompatibleInterfaceArrays$I1"); Class.forName("TestMeetIncompatibleInterfaceArrays$I2"); Class.forName("TestMeetIncompatibleInterfaceArrays$Helper"); for (int g = 0; g < 2; g++) { String baseClassName = "MeetIncompatibleInterfaceArrays"; boolean good = (g == 0) ? false : true; if (good) baseClassName += "Good"; for (int i = 0; i < 6; i++) { System.out.println(); System.out.println("Creating " + baseClassName + i + "ASM.class"); System.out.println("========================================" + "=" + "========="); // Create the "MeetIncompatibleInterfaceArraysASM" class generateTestClass(i, good); Class c = null; try { c = Class.forName(baseClassName + i + "ASM"); } catch (VerifyError ve) { if (i == 4) { System.out.println("OK - must be (" + ve.getMessage() + ")."); } else { throw ve; } continue; } // Call MeetIncompatibleInterfaceArraysASM.test() Method m = c.getMethod("test"); Method r = c.getMethod("run"); for (int j = 0; j < 3; j++) { System.out.println((j + 1) + ". invokation of " + baseClassName + i + "ASM.test() [should be " + tier[pass][j] + "]"); try { m.invoke(null); } catch (InvocationTargetException ite) { if (good) { throw ite; } else { if (ite.getCause() instanceof IncompatibleClassChangeError) { System.out.println(" OK - catched InvocationTargetException(" + ite.getCause().getMessage() + ")."); } else { throw ite; } } } } System.out.println("Method " + r + (WB.isMethodCompiled(r) ? " has" : " has not") + " been compiled."); if (!WB.isMethodCompiled(r)) { throw new Exception("Method " + r + " must be compiled!"); } } } } }