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