1 /*
   2  * Copyright (c) 2015 SAP SE. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /*
  25  * @test
  26  * @bug 8141551
  27  * @summary C2 can not handle returns with inccompatible interface arrays
  28  * @modules java.base/jdk.internal.org.objectweb.asm
  29  *          java.base/jdk.internal.misc
  30  * @library /testlibrary /test/lib
  31  * @build sun.hotspot.WhiteBox
  32  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  33  *                              sun.hotspot.WhiteBox$WhiteBoxPermission
  34  * @run main/othervm
  35  *        -Xbootclasspath/a:.
  36  *        -XX:+UnlockDiagnosticVMOptions
  37  *        -XX:+WhiteBoxAPI
  38  *        -Xbatch
  39  *        -XX:CompileThreshold=1
  40  *        -XX:-TieredCompilation
  41  *        -XX:CICompilerCount=1
  42  *        -XX:+PrintCompilation
  43  *        -XX:+PrintInlining
  44  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run
  45  *        -XX:CompileCommand=dontinline,TestMeetIncompatibleInterfaceArrays$Helper.createI2*
  46  *        -XX:CompileCommand=quiet
  47  *        TestMeetIncompatibleInterfaceArrays 0
  48  * @run main/othervm
  49  *        -Xbootclasspath/a:.
  50  *        -XX:+UnlockDiagnosticVMOptions
  51  *        -XX:+WhiteBoxAPI
  52  *        -Xbatch
  53  *        -XX:CompileThreshold=1
  54  *        -XX:-TieredCompilation
  55  *        -XX:CICompilerCount=1
  56  *        -XX:+PrintCompilation
  57  *        -XX:+PrintInlining
  58  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run
  59  *        -XX:CompileCommand=inline,TestMeetIncompatibleInterfaceArrays$Helper.createI2*
  60  *        -XX:CompileCommand=quiet
  61  *        TestMeetIncompatibleInterfaceArrays 1
  62  * @run main/othervm
  63  *        -Xbootclasspath/a:.
  64  *        -XX:+UnlockDiagnosticVMOptions
  65  *        -XX:+WhiteBoxAPI
  66  *        -Xbatch
  67  *        -XX:CompileThreshold=1
  68  *        -XX:Tier0InvokeNotifyFreqLog=0 -XX:Tier2InvokeNotifyFreqLog=0 -XX:Tier3InvokeNotifyFreqLog=0 -XX:Tier23InlineeNotifyFreqLog=0
  69  *        -XX:Tier3InvocationThreshold=2 -XX:Tier3MinInvocationThreshold=2 -XX:Tier3CompileThreshold=2
  70  *        -XX:Tier4InvocationThreshold=1 -XX:Tier4MinInvocationThreshold=1 -XX:Tier4CompileThreshold=1
  71  *        -XX:+TieredCompilation
  72  *        -XX:CICompilerCount=2
  73  *        -XX:+PrintCompilation
  74  *        -XX:+PrintInlining
  75  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*.run
  76  *        -XX:CompileCommand=compileonly,TestMeetIncompatibleInterfaceArrays$Helper.createI2*
  77  *        -XX:CompileCommand=inline,TestMeetIncompatibleInterfaceArrays$Helper.createI2*
  78  *        -XX:CompileCommand=quiet
  79  *        TestMeetIncompatibleInterfaceArrays 2
  80  *
  81  * @author volker.simonis@gmail.com
  82  */
  83 
  84 import java.io.FileOutputStream;
  85 import java.lang.reflect.InvocationTargetException;
  86 import java.lang.reflect.Method;
  87 import jdk.internal.org.objectweb.asm.ClassWriter;
  88 import jdk.internal.org.objectweb.asm.MethodVisitor;
  89 import static jdk.internal.org.objectweb.asm.Opcodes.*;
  90 import sun.hotspot.WhiteBox;
  91 
  92 public class TestMeetIncompatibleInterfaceArrays extends ClassLoader {
  93 
  94     private static final WhiteBox WB = WhiteBox.getWhiteBox();
  95 
  96     public static interface I1 { public String getName(); }
  97     public static interface I2 { public String getName(); }
  98     public static class I2C implements I2 { public String getName() { return "I2";} }
  99     public static class I21C implements I2, I1 { public String getName() { return "I2 and I1";} }
 100 
 101     public static class Helper {
 102         public static I2 createI2Array0() {
 103             return new I2C();
 104         }
 105         public static I2[] createI2Array1() {
 106             return new I2C[] { new I2C() };
 107         }
 108         public static I2[][] createI2Array2() {
 109             return new I2C[][] { new I2C[] { new I2C() } };
 110         }
 111         public static I2[][][] createI2Array3() {
 112             return new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } };
 113         }
 114         public static I2[][][][] createI2Array4() {
 115             return new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } };
 116         }
 117         public static I2[][][][][] createI2Array5() {
 118             return new I2C[][][][][] { new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } } };
 119         }
 120         public static I2 createI21Array0() {
 121             return new I21C();
 122         }
 123         public static I2[] createI21Array1() {
 124             return new I21C[] { new I21C() };
 125         }
 126         public static I2[][] createI21Array2() {
 127             return new I21C[][] { new I21C[] { new I21C() } };
 128         }
 129         public static I2[][][] createI21Array3() {
 130             return new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } };
 131         }
 132         public static I2[][][][] createI21Array4() {
 133             return new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } };
 134         }
 135         public static I2[][][][][] createI21Array5() {
 136             return new I21C[][][][][] { new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } } };
 137         }
 138     }
 139 
 140     // Location for the generated class files
 141     public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator;
 142 
 143     /*
 144      * With 'good == false' this helper method creates the following classes
 145      * (using the nested 'Helper' class and the nested interfaces 'I1' and 'I2').
 146      * For brevity I omit the enclosing class 'TestMeetIncompatibleInterfaceArrays' in the
 147      * following examples:
 148      *
 149      * public class MeetIncompatibleInterfaceArrays0ASM {
 150      *   public static I1 run() {
 151      *     return Helper.createI2Array0(); // returns I2
 152      *   }
 153      *   public static void test() {
 154      *     I1 i1 = run();
 155      *     System.out.println(i1.getName());
 156      *   }
 157      * }
 158      * public class MeetIncompatibleInterfaceArrays1ASM {
 159      *   public static I1[] run() {
 160      *     return Helper.createI2Array1(); // returns I2[]
 161      *   }
 162      *   public static void test() {
 163      *     I1[] i1 = run();
 164      *     System.out.println(i1[0].getName());
 165      *   }
 166      * }
 167      * ...
 168      * // MeetIncompatibleInterfaceArrays4ASM is special because it creates
 169      * // an illegal class which will be rejected by the verifier.
 170      * public class MeetIncompatibleInterfaceArrays4ASM {
 171      *   public static I1[][][][] run() {
 172      *     return Helper.createI2Array3(); // returns I1[][][] which gives a verifier error because return expects I1[][][][]
 173      *   }
 174      *   public static void test() {
 175      *     I1[][][][][] i1 = run();
 176      *     System.out.println(i1[0][0][0][0][0].getName());
 177      *   }
 178      * ...
 179      * public class MeetIncompatibleInterfaceArrays5ASM {
 180      *   public static I1[][][][][] run() {
 181      *     return Helper.createI2Array5(); // returns I2[][][][][]
 182      *   }
 183      *   public static void test() {
 184      *     I1[][][][][] i1 = run();
 185      *     System.out.println(i1[0][0][0][0][0].getName());
 186      *   }
 187      * }
 188      *
 189      * Notice that this is not legal Java code. We would have to use a cast in "run()" to make it legal:
 190      *
 191      *   public static I1[] run() {
 192      *     return (I1[])Helper.createI2Array1(); // returns I2[]
 193      *   }
 194      *
 195      * But in pure bytecode, the "run()" methods are perfectly legal:
 196      *
 197      *   public static I1[] run();
 198      *     Code:
 199      *       0: invokestatic  #16  // Method Helper.createI2Array1:()[LI2;
 200      *       3: areturn
 201      *
 202      * The "test()" method calls the "getName()" function from I1 on the objects returned by "run()".
 203      * This will epectedly fail with an "IncompatibleClassChangeError" because the objects returned
 204      * by "run()" (and by createI2Array()) are actually of type "I2C" and only implement "I2" but not "I1".
 205      *
 206      *
 207      * With 'good == true' this helper method will create the following classes:
 208      *
 209      * public class MeetIncompatibleInterfaceArraysGood0ASM {
 210      *   public static I1 run() {
 211      *     return Helper.createI21Array0(); // returns I2
 212      *   }
 213      *   public static void test() {
 214      *     I1 i1 = run();
 215      *     System.out.println(i1.getName());
 216      *   }
 217      * }
 218      *
 219      * Calling "test()" on these objects will succeed and output "I2 and I1" because now the "run()"
 220      * method calls "createI21Array()" which actually return an object (or an array of objects) of
 221      * type "I21C" which implements both "I2" and "I1".
 222      *
 223      * Notice that at the bytecode level, the code for the "run()" and "test()" methods in
 224      * "MeetIncompatibleInterfaceArraysASM" and "MeetIncompatibleInterfaceArraysGoodASM" look exactly
 225      * the same. I.e. the verifier has no chance to verify if the I2 object returned by "createI1Array()"
 226      * or "createI21Array()" implements "I1" or not. That's actually the reason why both versions of
 227      * generated classes are legal from a verifier point of view.
 228      *
 229      */
 230     static void generateTestClass(int dim, boolean good) throws Exception {
 231         String baseClassName = "MeetIncompatibleInterfaceArrays";
 232         if (good)
 233             baseClassName += "Good";
 234         String createName = "createI2" + (good ? "1" : "") + "Array";
 235         String a = "";
 236         for (int i = 0; i < dim; i++)
 237             a += "[";
 238         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 239         cw.visit(V1_8, ACC_PUBLIC, baseClassName + dim + "ASM", null, "java/lang/Object", null);
 240         MethodVisitor constr = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 241         constr.visitCode();
 242         constr.visitVarInsn(ALOAD, 0);
 243         constr.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
 244         constr.visitInsn(RETURN);
 245         constr.visitMaxs(0, 0);
 246         constr.visitEnd();
 247         MethodVisitor run = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "run",
 248                 "()" + a + "LTestMeetIncompatibleInterfaceArrays$I1;", null, null);
 249         run.visitCode();
 250         if (dim == 4) {
 251             run.visitMethodInsn(INVOKESTATIC, "TestMeetIncompatibleInterfaceArrays$Helper", createName + 3,
 252                     "()" + "[[[" + "LTestMeetIncompatibleInterfaceArrays$I2;", false);
 253         } else {
 254             run.visitMethodInsn(INVOKESTATIC, "TestMeetIncompatibleInterfaceArrays$Helper", createName + dim,
 255                     "()" + a + "LTestMeetIncompatibleInterfaceArrays$I2;", false);
 256         }
 257         run.visitInsn(ARETURN);
 258         run.visitMaxs(0, 0);
 259         run.visitEnd();
 260         MethodVisitor test = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null);
 261         test.visitCode();
 262         test.visitMethodInsn(INVOKESTATIC, baseClassName + dim + "ASM", "run",
 263                 "()" + a + "LTestMeetIncompatibleInterfaceArrays$I1;", false);
 264         test.visitVarInsn(ASTORE, 0);
 265         if (dim > 0) {
 266             test.visitVarInsn(ALOAD, 0);
 267             for (int i = 1; i <= dim; i++) {
 268                 test.visitInsn(ICONST_0);
 269                 test.visitInsn(AALOAD);
 270             }
 271             test.visitVarInsn(ASTORE, 1);
 272         }
 273         test.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
 274         test.visitVarInsn(ALOAD, dim > 0 ? 1 : 0);
 275         test.visitMethodInsn(INVOKEINTERFACE, "TestMeetIncompatibleInterfaceArrays$I1", "getName",
 276                 "()Ljava/lang/String;", true);
 277         test.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
 278         test.visitInsn(RETURN);
 279         test.visitMaxs(0, 0);
 280         test.visitEnd();
 281 
 282         // Get the bytes of the class..
 283         byte[] b = cw.toByteArray();
 284         // ..and write them into a class file (for debugging)
 285         FileOutputStream fos = new FileOutputStream(PATH + baseClassName + dim + "ASM.class");
 286         fos.write(b);
 287         fos.close();
 288 
 289     }
 290 
 291     public static String[][] tier = { { "interpreted", "C2 (tier 4) without inlining", "C2 (tier4) without inlining" },
 292             { "interpreted", "C2 (tier 4) with inlining", "C2 (tier4) with inlining" },
 293             { "interpreted", "C1 (tier 3) with inlining", "C2 (tier4) with inlining" } };
 294 
 295     public static void main(String[] args) throws Exception {
 296         final int pass = Integer.parseInt(args.length > 0 ? args[0] : "0");
 297 
 298         // Load and initialize some classes required for compilation
 299         Class.forName("TestMeetIncompatibleInterfaceArrays$I1");
 300         Class.forName("TestMeetIncompatibleInterfaceArrays$I2");
 301         Class.forName("TestMeetIncompatibleInterfaceArrays$Helper");
 302 
 303         for (int g = 0; g < 2; g++) {
 304             String baseClassName = "MeetIncompatibleInterfaceArrays";
 305             boolean good = (g == 0) ? false : true;
 306             if (good)
 307                 baseClassName += "Good";
 308             for (int i = 0; i < 6; i++) {
 309                 System.out.println();
 310                 System.out.println("Creating " + baseClassName + i + "ASM.class");
 311                 System.out.println("========================================" + "=" + "=========");
 312                 // Create the "MeetIncompatibleInterfaceArrays<i>ASM" class
 313                 generateTestClass(i, good);
 314                 Class<?> c = null;
 315                 try {
 316                     c = Class.forName(baseClassName + i + "ASM");
 317                 } catch (VerifyError ve) {
 318                     if (i == 4) {
 319                         System.out.println("OK - must be (" + ve.getMessage() + ").");
 320                     } else {
 321                         throw ve;
 322                     }
 323                     continue;
 324                 }
 325                 // Call MeetIncompatibleInterfaceArrays<i>ASM.test()
 326                 Method m = c.getMethod("test");
 327                 Method r = c.getMethod("run");
 328                 for (int j = 0; j < 3; j++) {
 329                     System.out.println((j + 1) + ". invokation of " + baseClassName + i + "ASM.test() [should be "
 330                             + tier[pass][j] + "]");
 331                     try {
 332                         m.invoke(null);
 333                     } catch (InvocationTargetException ite) {
 334                         if (good) {
 335                             throw ite;
 336                         } else {
 337                             if (ite.getCause() instanceof IncompatibleClassChangeError) {
 338                                 System.out.println("  OK - catched InvocationTargetException("
 339                                         + ite.getCause().getMessage() + ").");
 340                             } else {
 341                                 throw ite;
 342                             }
 343                         }
 344                     }
 345                 }
 346                 System.out.println("Method " + r + (WB.isMethodCompiled(r) ? " has" : " has not") + " been compiled.");
 347                 if (!WB.isMethodCompiled(r)) {
 348                     throw new Exception("Method " + r + " must be compiled!");
 349                 }
 350             }
 351         }
 352     }
 353 }