1 /*
   2  * Copyright (c) 2018 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 /test/lib /
  31  *
  32  * @build sun.hotspot.WhiteBox
  33  * @run driver ClassFileInstaller sun.hotspot.WhiteBox
  34  *                                sun.hotspot.WhiteBox$WhiteBoxPermission
  35  * @run main/othervm
  36  *        -Xbootclasspath/a:.
  37  *        -XX:+UnlockDiagnosticVMOptions
  38  *        -XX:+WhiteBoxAPI
  39  *        -Xbatch
  40  *        -XX:-TieredCompilation
  41  *        -XX:TieredStopAtLevel=4
  42  *        -XX:CICompilerCount=1
  43  *        -XX:+PrintCompilation
  44  *        -XX:+PrintInlining
  45  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*::run
  46  *        -XX:CompileCommand=dontinline,compiler.types.TestMeetIncompatibleInterfaceArrays$Helper::createI2*
  47  *        -XX:CompileCommand=quiet
  48  *        compiler.types.TestMeetIncompatibleInterfaceArrays 0
  49  * @run main/othervm
  50  *        -Xbootclasspath/a:.
  51  *        -XX:+UnlockDiagnosticVMOptions
  52  *        -XX:+WhiteBoxAPI
  53  *        -Xbatch
  54  *        -XX:-TieredCompilation
  55  *        -XX:TieredStopAtLevel=4
  56  *        -XX:CICompilerCount=1
  57  *        -XX:+PrintCompilation
  58  *        -XX:+PrintInlining
  59  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*::run
  60  *        -XX:CompileCommand=inline,compiler.types.TestMeetIncompatibleInterfaceArrays$Helper::createI2*
  61  *        -XX:CompileCommand=quiet
  62  *        compiler.types.TestMeetIncompatibleInterfaceArrays 1
  63  * @run main/othervm
  64  *        -Xbootclasspath/a:.
  65  *        -XX:+UnlockDiagnosticVMOptions
  66  *        -XX:+WhiteBoxAPI
  67  *        -Xbatch
  68  *        -XX:+TieredCompilation
  69  *        -XX:TieredStopAtLevel=4
  70  *        -XX:CICompilerCount=2
  71  *        -XX:+PrintCompilation
  72  *        -XX:+PrintInlining
  73  *        -XX:CompileCommand=compileonly,MeetIncompatibleInterfaceArrays*::run
  74  *        -XX:CompileCommand=compileonly,compiler.types.TestMeetIncompatibleInterfaceArrays$Helper::createI2*
  75  *        -XX:CompileCommand=inline,compiler.types.TestMeetIncompatibleInterfaceArrays$Helper::createI2*
  76  *        -XX:CompileCommand=quiet
  77  *        compiler.types.TestMeetIncompatibleInterfaceArrays 2
  78  *
  79  * @author volker.simonis@gmail.com
  80  */
  81 
  82 package compiler.types;
  83 
  84 import compiler.whitebox.CompilerWhiteBoxTest;
  85 import jdk.internal.org.objectweb.asm.ClassWriter;
  86 import jdk.internal.org.objectweb.asm.MethodVisitor;
  87 import sun.hotspot.WhiteBox;
  88 
  89 import java.io.FileOutputStream;
  90 import java.lang.reflect.InvocationTargetException;
  91 import java.lang.reflect.Method;
  92 
  93 import static jdk.internal.org.objectweb.asm.Opcodes.AALOAD;
  94 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
  95 import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC;
  96 import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD;
  97 import static jdk.internal.org.objectweb.asm.Opcodes.ARETURN;
  98 import static jdk.internal.org.objectweb.asm.Opcodes.ASTORE;
  99 import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC;
 100 import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0;
 101 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE;
 102 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL;
 103 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
 104 import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 105 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
 106 import static jdk.internal.org.objectweb.asm.Opcodes.V1_8;
 107 
 108 public class TestMeetIncompatibleInterfaceArrays extends ClassLoader {
 109 
 110     private static final WhiteBox WB = WhiteBox.getWhiteBox();
 111 
 112     public static interface I1 { public String getName(); }
 113     public static interface I2 { public String getName(); }
 114     public static class I2C implements I2 { public String getName() { return "I2";} }
 115     public static class I21C implements I2, I1 { public String getName() { return "I2 and I1";} }
 116 
 117     public static class Helper {
 118         public static I2 createI2Array0() {
 119             return new I2C();
 120         }
 121         public static I2[] createI2Array1() {
 122             return new I2C[] { new I2C() };
 123         }
 124         public static I2[][] createI2Array2() {
 125             return new I2C[][] { new I2C[] { new I2C() } };
 126         }
 127         public static I2[][][] createI2Array3() {
 128             return new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } };
 129         }
 130         public static I2[][][][] createI2Array4() {
 131             return new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } };
 132         }
 133         public static I2[][][][][] createI2Array5() {
 134             return new I2C[][][][][] { new I2C[][][][] { new I2C[][][] { new I2C[][] { new I2C[] { new I2C() } } } } };
 135         }
 136         public static I2 createI21Array0() {
 137             return new I21C();
 138         }
 139         public static I2[] createI21Array1() {
 140             return new I21C[] { new I21C() };
 141         }
 142         public static I2[][] createI21Array2() {
 143             return new I21C[][] { new I21C[] { new I21C() } };
 144         }
 145         public static I2[][][] createI21Array3() {
 146             return new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } };
 147         }
 148         public static I2[][][][] createI21Array4() {
 149             return new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } };
 150         }
 151         public static I2[][][][][] createI21Array5() {
 152             return new I21C[][][][][] { new I21C[][][][] { new I21C[][][] { new I21C[][] { new I21C[] { new I21C() } } } } };
 153         }
 154     }
 155 
 156     // Location for the generated class files
 157     public static final String PATH = System.getProperty("test.classes", ".") + java.io.File.separator;
 158 
 159     /*
 160      * With 'good == false' this helper method creates the following classes
 161      * (using the nested 'Helper' class and the nested interfaces 'I1' and 'I2').
 162      * For brevity I omit the enclosing class 'TestMeetIncompatibleInterfaceArrays' in the
 163      * following examples:
 164      *
 165      * public class MeetIncompatibleInterfaceArrays0ASM {
 166      *   public static I1 run() {
 167      *     return Helper.createI2Array0(); // returns I2
 168      *   }
 169      *   public static void test() {
 170      *     I1 i1 = run();
 171      *     System.out.println(i1.getName());
 172      *   }
 173      * }
 174      * public class MeetIncompatibleInterfaceArrays1ASM {
 175      *   public static I1[] run() {
 176      *     return Helper.createI2Array1(); // returns I2[]
 177      *   }
 178      *   public static void test() {
 179      *     I1[] i1 = run();
 180      *     System.out.println(i1[0].getName());
 181      *   }
 182      * }
 183      * ...
 184      * // MeetIncompatibleInterfaceArrays4ASM is special because it creates
 185      * // an illegal class which will be rejected by the verifier.
 186      * public class MeetIncompatibleInterfaceArrays4ASM {
 187      *   public static I1[][][][] run() {
 188      *     return Helper.createI2Array3(); // returns I1[][][] which gives a verifier error because return expects I1[][][][]
 189      *   }
 190      *   public static void test() {
 191      *     I1[][][][] i1 = run();
 192      *     System.out.println(i1[0][0][0][0].getName());
 193      *   }
 194      * ...
 195      * public class MeetIncompatibleInterfaceArrays5ASM {
 196      *   public static I1[][][][][] run() {
 197      *     return Helper.createI2Array5(); // returns I2[][][][][]
 198      *   }
 199      *   public static void test() {
 200      *     I1[][][][][] i1 = run();
 201      *     System.out.println(i1[0][0][0][0][0].getName());
 202      *   }
 203      * }
 204      *
 205      * Notice that this is not legal Java code. We would have to use a cast in "run()" to make it legal:
 206      *
 207      *   public static I1[] run() {
 208      *     return (I1[])Helper.createI2Array1(); // returns I2[]
 209      *   }
 210      *
 211      * But in pure bytecode, the "run()" methods are perfectly legal:
 212      *
 213      *   public static I1[] run();
 214      *     Code:
 215      *       0: invokestatic  #16  // Method Helper.createI2Array1:()[LI2;
 216      *       3: areturn
 217      *
 218      * The "test()" method calls the "getName()" function from I1 on the objects returned by "run()".
 219      * This will epectedly fail with an "IncompatibleClassChangeError" because the objects returned
 220      * by "run()" (and by createI2Array()) are actually of type "I2C" and only implement "I2" but not "I1".
 221      *
 222      *
 223      * With 'good == true' this helper method will create the following classes:
 224      *
 225      * public class MeetIncompatibleInterfaceArraysGood0ASM {
 226      *   public static I1 run() {
 227      *     return Helper.createI21Array0(); // returns I2
 228      *   }
 229      *   public static void test() {
 230      *     I1 i1 = run();
 231      *     System.out.println(i1.getName());
 232      *   }
 233      * }
 234      *
 235      * Calling "test()" on these objects will succeed and output "I2 and I1" because now the "run()"
 236      * method calls "createI21Array()" which actually return an object (or an array of objects) of
 237      * type "I21C" which implements both "I2" and "I1".
 238      *
 239      * Notice that at the bytecode level, the code for the "run()" and "test()" methods in
 240      * "MeetIncompatibleInterfaceArraysASM" and "MeetIncompatibleInterfaceArraysGoodASM" look exactly
 241      * the same. I.e. the verifier has no chance to verify if the I2 object returned by "createI1Array()"
 242      * or "createI21Array()" implements "I1" or not. That's actually the reason why both versions of
 243      * generated classes are legal from a verifier point of view.
 244      *
 245      */
 246     static void generateTestClass(int dim, boolean good) throws Exception {
 247         String baseClassName = "MeetIncompatibleInterfaceArrays";
 248         if (good)
 249             baseClassName += "Good";
 250         String createName = "createI2" + (good ? "1" : "") + "Array";
 251         String a = "";
 252         for (int i = 0; i < dim; i++)
 253             a += "[";
 254         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
 255         cw.visit(V1_8, ACC_PUBLIC, baseClassName + dim + "ASM", null, "java/lang/Object", null);
 256         MethodVisitor constr = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
 257         constr.visitCode();
 258         constr.visitVarInsn(ALOAD, 0);
 259         constr.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
 260         constr.visitInsn(RETURN);
 261         constr.visitMaxs(0, 0);
 262         constr.visitEnd();
 263         MethodVisitor run = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "run",
 264                 "()" + a + "Lcompiler/types/TestMeetIncompatibleInterfaceArrays$I1;", null, null);
 265         run.visitCode();
 266         if (dim == 4) {
 267             run.visitMethodInsn(INVOKESTATIC, "compiler/types/TestMeetIncompatibleInterfaceArrays$Helper", createName + 3,
 268                     "()" + "[[[" + "Lcompiler/types/TestMeetIncompatibleInterfaceArrays$I2;", false);
 269         } else {
 270             run.visitMethodInsn(INVOKESTATIC, "compiler/types/TestMeetIncompatibleInterfaceArrays$Helper", createName + dim,
 271                     "()" + a + "Lcompiler/types/TestMeetIncompatibleInterfaceArrays$I2;", false);
 272         }
 273         run.visitInsn(ARETURN);
 274         run.visitMaxs(0, 0);
 275         run.visitEnd();
 276         MethodVisitor test = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "test", "()V", null, null);
 277         test.visitCode();
 278         test.visitMethodInsn(INVOKESTATIC, baseClassName + dim + "ASM", "run",
 279                 "()" + a + "Lcompiler/types/TestMeetIncompatibleInterfaceArrays$I1;", false);
 280         test.visitVarInsn(ASTORE, 0);
 281         if (dim > 0) {
 282             test.visitVarInsn(ALOAD, 0);
 283             for (int i = 1; i <= dim; i++) {
 284                 test.visitInsn(ICONST_0);
 285                 test.visitInsn(AALOAD);
 286             }
 287             test.visitVarInsn(ASTORE, 1);
 288         }
 289         test.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
 290         test.visitVarInsn(ALOAD, dim > 0 ? 1 : 0);
 291         test.visitMethodInsn(INVOKEINTERFACE, "compiler/types/TestMeetIncompatibleInterfaceArrays$I1", "getName",
 292                 "()Ljava/lang/String;", true);
 293         test.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
 294         test.visitInsn(RETURN);
 295         test.visitMaxs(0, 0);
 296         test.visitEnd();
 297 
 298         // Get the bytes of the class..
 299         byte[] b = cw.toByteArray();
 300         // ..and write them into a class file (for debugging)
 301         FileOutputStream fos = new FileOutputStream(PATH + baseClassName + dim + "ASM.class");
 302         fos.write(b);
 303         fos.close();
 304 
 305     }
 306 
 307     public static String[][] tier = { { "interpreted (tier 0)",
 308                                         "C2 (tier 4) without inlining",
 309                                         "C2 (tier 4) without inlining" },
 310                                       { "interpreted (tier 0)",
 311                                         "C2 (tier 4) with inlining",
 312                                         "C2 (tier 4) with inlining" },
 313                                       { "interpreted (tier 0)",
 314                                         "C1 (tier 3) with inlining",
 315                                         "C2 (tier 4) with inlining" } };
 316 
 317     public static int[][] level = { { CompilerWhiteBoxTest.COMP_LEVEL_NONE,
 318                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION,
 319                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION },
 320                                     { CompilerWhiteBoxTest.COMP_LEVEL_NONE,
 321                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION,
 322                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION },
 323                                     { CompilerWhiteBoxTest.COMP_LEVEL_NONE,
 324                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_PROFILE,
 325                                       CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION } };
 326 
 327     public static void main(String[] args) throws Exception {
 328         final int pass = Integer.parseInt(args.length > 0 ? args[0] : "0");
 329 
 330         // Load and initialize some classes required for compilation
 331         Class.forName("compiler.types.TestMeetIncompatibleInterfaceArrays$I1");
 332         Class.forName("compiler.types.TestMeetIncompatibleInterfaceArrays$I2");
 333         Class.forName("compiler.types.TestMeetIncompatibleInterfaceArrays$Helper");
 334 
 335         for (int g = 0; g < 2; g++) {
 336             String baseClassName = "MeetIncompatibleInterfaceArrays";
 337             boolean good = (g == 0) ? false : true;
 338             if (good)
 339                 baseClassName += "Good";
 340             for (int i = 0; i < 6; i++) {
 341                 System.out.println();
 342                 System.out.println("Creating " + baseClassName + i + "ASM.class");
 343                 System.out.println("========================================" + "=" + "=========");
 344                 // Create the "MeetIncompatibleInterfaceArrays<i>ASM" class
 345                 generateTestClass(i, good);
 346                 Class<?> c = null;
 347                 try {
 348                     c = Class.forName(baseClassName + i + "ASM");
 349                 } catch (VerifyError ve) {
 350                     if (i == 4) {
 351                         System.out.println("OK - must be (" + ve.getMessage() + ").");
 352                     } else {
 353                         throw ve;
 354                     }
 355                     continue;
 356                 }
 357                 // Call MeetIncompatibleInterfaceArrays<i>ASM.test()
 358                 Method m = c.getMethod("test");
 359                 Method r = c.getMethod("run");
 360                 for (int j = 0; j < 3; j++) {
 361                     System.out.println((j + 1) + ". invokation of " + baseClassName + i + "ASM.test() [::" +
 362                                        r.getName() + "() should be '" + tier[pass][j] + "' compiled]");
 363 
 364                     WB.enqueueMethodForCompilation(r, level[pass][j]);
 365 
 366                     try {
 367                         m.invoke(null);
 368                     } catch (InvocationTargetException ite) {
 369                         if (good) {
 370                             throw ite;
 371                         } else {
 372                             if (ite.getCause() instanceof IncompatibleClassChangeError) {
 373                                 System.out.println("  OK - catched InvocationTargetException("
 374                                         + ite.getCause().getMessage() + ").");
 375                             } else {
 376                                 throw ite;
 377                             }
 378                         }
 379                     }
 380 
 381                     int r_comp_level = WB.getMethodCompilationLevel(r);
 382                     System.out.println("   invokation of " + baseClassName + i + "ASM.test() [::" +
 383                                        r.getName() + "() was compiled at tier " + r_comp_level + "]");
 384 
 385                     if (r_comp_level != level[pass][j]) {
 386                       throw new Exception("Method " + r + " must be compiled at tier " + level[pass][j] +
 387                                           " but was compiled at " + r_comp_level + " instead!");
 388                     }
 389 
 390                     WB.deoptimizeMethod(r);
 391                 }
 392             }
 393         }
 394     }
 395 }