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  * @requires vm.compMode == "Xmixed" & vm.flavor == "server"
 29  * @modules java.base/jdk.internal.org.objectweb.asm
 30  *          java.base/jdk.internal.misc
 31  * @library /test/lib /
 32  *
 33  * @build jdk.test.whitebox.WhiteBox
 34  * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
 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 jdk.test.whitebox.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 expectedly 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                     // Skip Profiling compilation (C1) when Tiered is disabled.
365                     boolean profile = (level[pass][j] == CompilerWhiteBoxTest.COMP_LEVEL_FULL_PROFILE);
366                     if (profile && CompilerWhiteBoxTest.skipOnTieredCompilation(false)) {
367                         continue;
368                     }
369 
370                     WB.enqueueMethodForCompilation(r, level[pass][j]);
371 
372                     try {
373                         m.invoke(null);
374                     } catch (InvocationTargetException ite) {
375                         if (good) {
376                             throw ite;
377                         } else {
378                             if (ite.getCause() instanceof IncompatibleClassChangeError) {
379                                 System.out.println("  OK - catched InvocationTargetException("
380                                         + ite.getCause().getMessage() + ").");
381                             } else {
382                                 throw ite;
383                             }
384                         }
385                     }
386 
387                     int r_comp_level = WB.getMethodCompilationLevel(r);
388                     System.out.println("   invokation of " + baseClassName + i + "ASM.test() [::" +
389                                        r.getName() + "() was compiled at tier " + r_comp_level + "]");
390 
391                     if (r_comp_level != level[pass][j]) {
392                       throw new Exception("Method " + r + " must be compiled at tier " + level[pass][j] +
393                                           " but was compiled at " + r_comp_level + " instead!");
394                     }
395 
396                     WB.deoptimizeMethod(r);
397                 }
398             }
399         }
400     }
401 }