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