/* * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /** * @test * @bug 8025260 8016839 * @summary Ensure that AbstractMethodError and IllegalAccessError are thrown appropriately, not NullPointerException * @modules java.base/jdk.internal.org.objectweb.asm * @library / . * * @build p.* * @run main/othervm compiler.jsr292.methodHandleExceptions.TestAMEnotNPE * @run main/othervm -Xint compiler.jsr292.methodHandleExceptions.TestAMEnotNPE * @run main/othervm -Xcomp compiler.jsr292.methodHandleExceptions.TestAMEnotNPE */ package compiler.jsr292.methodHandleExceptions; import p.Dok; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.Handle; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.Opcodes; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; import static jdk.internal.org.objectweb.asm.Opcodes.ALOAD; import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN; import static jdk.internal.org.objectweb.asm.Opcodes.LLOAD; import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; import static jdk.internal.org.objectweb.asm.Opcodes.V1_8; public class TestAMEnotNPE { static boolean writeJarFiles = false; static boolean readJarFiles = false; /** * Optional command line parameter (any case-insensitive prefix of) * "writejarfiles" or "readjarfiles". * * "Writejarfiles" creates a jar file for each different set of tested classes. * "Readjarfiles" causes the classloader to use the copies of the classes * found in the corresponding jar files. * * Jarfilenames look something like pD_ext_pF (p.D extends p.F) * and qD_m_pp_imp_pI (q.D with package-private m implements p.I) * */ public static void main(String args[]) throws Throwable { ArrayList<Throwable> lt = new ArrayList<Throwable>(); if (args.length > 0) { String a0 = args[0].toLowerCase(); if (a0.length() > 0) { writeJarFiles = ("writejarfiles").startsWith(a0); readJarFiles = ("readjarfiles").startsWith(a0); } if (!(writeJarFiles || readJarFiles)) { throw new Error("Command line parameter (if any) should be prefix of writeJarFiles or readJarFiles"); } } try { System.out.println("TRYING p.D.m PRIVATE interface-invoked as p.I.m, p.D extends p.F, p.F.m FINAL"); tryAndCheckThrown(lt, bytesForDprivateSubWhat("p/F"), "p.D extends p.F (p.F implements p.I, FINAL public m), private m", IllegalAccessError.class, "pD_ext_pF"); // We'll take either a VerifyError (pre 2013-11-30) // or an IllegalAccessError (post 2013-11-22) } catch (VerifyError ve) { System.out.println("Saw expected VerifyError " + ve); } System.out.println(); System.out.println("TRYING p.D.m PRIVATE interface-invoked as p.I.m, p.D extends p.E"); tryAndCheckThrown(lt, bytesForDprivateSubWhat("p/E"), "p.D extends p.E (p.E implements p.I, public m), private m", IllegalAccessError.class, "pD_ext_pE"); System.out.println("TRYING p.D.m ABSTRACT interface-invoked as p.I.m"); tryAndCheckThrown(lt, bytesForD(), "D extends abstract C, no m", AbstractMethodError.class, "pD_ext_pC"); System.out.println("TRYING q.D.m PACKAGE interface-invoked as p.I.m"); tryAndCheckThrown(lt, "q.D", bytesForDsomeAccess("q/D", 0), "q.D implements p.I, protected m", IllegalAccessError.class, "qD_m_pp_imp_pI"); // Note jar file name is used in the plural-arg case. System.out.println("TRYING p.D.m PRIVATE interface-invoked as p.I.m"); tryAndCheckThrown(lt, bytesForDsomeAccess("p/D", ACC_PRIVATE), "p.D implements p.I, private m", IllegalAccessError.class, "pD_m_pri_imp_pI"); // Plural-arg test. System.out.println("TRYING p.D.m PRIVATE MANY ARG interface-invoked as p.I.m"); tryAndCheckThrownMany(lt, bytesForDsomeAccess("p/D", ACC_PRIVATE), "p.D implements p.I, private m", IllegalAccessError.class); if (lt.size() > 0) { System.out.flush(); Thread.sleep(250); // This de-interleaves output and error in Netbeans, sigh. for (Throwable th : lt) System.err.println(th); throw new Error("Test failed, there were " + lt.size() + " failures listed above"); } else { System.out.println("ALL PASS, HOORAY!"); } } /** * The bytes for D, a NOT abstract class extending abstract class C without * supplying an implementation for abstract method m. There is a default * method in the interface I, but it should lose to the abstract class. * * @return * @throws Exception */ public static byte[] bytesForD() throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "p/D", null, "p/C", null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "p/C", "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } /** * The bytes for D, implements I, does not extend C, declares m()I with * access method_acc. * * @param d_name Name of class defined * @param method_acc Accessibility of that class's method m. * @return * @throws Exception */ public static byte[] bytesForDsomeAccess(String d_name, int method_acc) throws Exception { return bytesForSomeDsubSomethingSomeAccess(d_name, "java/lang/Object", method_acc); } /** * The bytes for D implements I, extends some class, declares m()I as * private. * * Invokeinterface of I.m applied to this D should throw IllegalAccessError * * @param sub_what The name of the class that D will extend. * @return * @throws Exception */ public static byte[] bytesForDprivateSubWhat(String sub_what) throws Exception { return bytesForSomeDsubSomethingSomeAccess("p/D", sub_what, ACC_PRIVATE); } /** * Returns the bytes for a class with name d_name (presumably "D" in some * package), extending some class with name sub_what, implementing p.I, * and defining two methods m() and m(11args) with access method_acc. * * @param d_name Name of class that is defined * @param sub_what Name of class that it extends * @param method_acc Accessibility of method(s) m in defined class. * @return * @throws Exception */ public static byte[] bytesForSomeDsubSomethingSomeAccess (String d_name, String sub_what, int method_acc) throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; String[] interfaces = {"p/I"}; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, d_name, null, sub_what, interfaces); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, sub_what, "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } // int m() {return 3;} { mv = cw.visitMethod(method_acc, "m", "()I", null, null); mv.visitCode(); mv.visitLdcInsn(new Integer(3)); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } // int m(11args) {return 3;} { mv = cw.visitMethod(method_acc, "m", "(BCSIJ" + "Ljava/lang/Object;" + "Ljava/lang/Object;" + "Ljava/lang/Object;" + "Ljava/lang/Object;" + "Ljava/lang/Object;" + "Ljava/lang/Object;" + ")I", null, null); mv.visitCode(); mv.visitLdcInsn(new Integer(3)); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } /** * The bytecodes for a class p/T defining a methods test() and test(11args) * that contain an invokeExact of a particular methodHandle, I.m. * * Test will be passed values that may imperfectly implement I, * and thus may in turn throw exceptions. * * @return * @throws Exception */ public static byte[] bytesForT() throws Exception { ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "p/T", null, "java/lang/Object", null); { mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } // static int test(I) { mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "test", "(Lp/I;)I", null, null); mv.visitCode(); mv.visitLdcInsn(new Handle(Opcodes.H_INVOKEINTERFACE, "p/I", "m", "()I")); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "(Lp/I;)I"); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } // static int test(I,11args) { mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "test", "(Lp/I;BCSIJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I", null, null); mv.visitCode(); mv.visitLdcInsn(new Handle(Opcodes.H_INVOKEINTERFACE, "p/I", "m", "(BCSIJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I")); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ILOAD, 1); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ILOAD, 3); mv.visitVarInsn(ILOAD, 4); mv.visitVarInsn(LLOAD, 5); mv.visitVarInsn(ALOAD, 7); mv.visitVarInsn(ALOAD, 8); mv.visitVarInsn(ALOAD, 9); mv.visitVarInsn(ALOAD, 10); mv.visitVarInsn(ALOAD, 11); mv.visitVarInsn(ALOAD, 12); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "(Lp/I;BCSIJLjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)I"); mv.visitInsn(IRETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } private static void tryAndCheckThrown( List<Throwable> lt, byte[] dBytes, String what, Class<?> expected, String jar_name) throws Throwable { tryAndCheckThrown(lt, "p.D", dBytes, what, expected, jar_name); } private static void tryAndCheckThrown(List<Throwable> lt, String d_name, byte[] dBytes, String what, Class<?> expected, String jar_name) throws Throwable { System.out.println("Methodhandle invokeExact I.m() for instance of " + what); ByteClassLoader bcl1 = new ByteClassLoader(jar_name, readJarFiles, writeJarFiles); try { Class<?> d1 = bcl1.loadBytes(d_name, dBytes); Class<?> t1 = bcl1.loadBytes("p.T", bytesForT()); invokeTest(t1, d1, expected, lt); } finally { // Not necessary for others -- all class files are written in this call. // (unless the VM crashes first). bcl1.close(); } System.out.println("Reflection invoke I.m() for instance of " + what); ByteClassLoader bcl3 = new ByteClassLoader(jar_name, readJarFiles, false); Class<?> d3 = bcl3.loadBytes(d_name, dBytes); Class<?> t3 = bcl3.loadClass("p.Treflect"); invokeTest(t3, d3, expected, lt); System.out.println("Bytecode invokeInterface I.m() for instance of " + what); ByteClassLoader bcl2 = new ByteClassLoader(jar_name, readJarFiles, false); Class<?> d2 = bcl2.loadBytes(d_name, dBytes); Class<?> t2 = bcl2.loadClass("p.Tdirect"); badGoodBadGood(t2, d2, expected, lt); } private static void invokeTest(Class<?> t, Class<?> d, Class<?> expected, List<Throwable> lt) throws Throwable { try { Method m = t.getMethod("test", p.I.class); Object o = d.newInstance(); Object result = m.invoke(null, o); if (expected != null) { System.out.println("FAIL, Expected " + expected.getName() + " wrapped in InvocationTargetException, but nothing was thrown"); lt.add(new Error("Exception " + expected.getName() + " was not thrown")); } else { System.out.println("PASS, saw expected return."); } } catch (InvocationTargetException e) { Throwable th = e.getCause(); th.printStackTrace(System.out); if (expected != null) { if (expected.isInstance(th)) { System.out.println("PASS, saw expected exception (" + expected.getName() + ")."); } else { System.out.println("FAIL, Expected " + expected.getName() + " wrapped in InvocationTargetException, saw " + th); lt.add(th); } } else { System.out.println("FAIL, expected no exception, saw " + th); lt.add(th); } } System.out.println(); } /* Many-arg versions of above */ private static void tryAndCheckThrownMany(List<Throwable> lt, byte[] dBytes, String what, Class<?> expected) throws Throwable { System.out.println("Methodhandle invokeExact I.m(11params) for instance of " + what); ByteClassLoader bcl1 = new ByteClassLoader("p.D", readJarFiles, false); try { Class<?> d1 = bcl1.loadBytes("p.D", dBytes); Class<?> t1 = bcl1.loadBytes("p.T", bytesForT()); invokeTestMany(t1, d1, expected, lt); } finally { bcl1.close(); // Not necessary for others -- all class files are written in this call. } { System.out.println("Bytecode invokeInterface I.m(11params) for instance of " + what); ByteClassLoader bcl2 = new ByteClassLoader("pD_m_pri_imp_pI", readJarFiles, false); Class<?> d2 = bcl2.loadBytes("p.D", dBytes); Class<?> t2 = bcl2.loadClass("p.Tdirect"); badGoodBadGoodMany(t2, d2, expected, lt); } { System.out.println("Reflection invokeInterface I.m(11params) for instance of " + what); ByteClassLoader bcl2 = new ByteClassLoader("pD_m_pri_imp_pI", readJarFiles, false); Class<?> d2 = bcl2.loadBytes("p.D", dBytes); Class<?> t2 = bcl2.loadClass("p.Treflect"); invokeTestMany(t2, d2, expected, lt); } } private static void invokeTestMany(Class<?> t, Class<?> d, Class<?> expected, List<Throwable> lt) throws Throwable { try { Method m = t.getMethod("test", p.I.class, Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Object.class, Object.class, Object.class, Object.class, Object.class, Object.class); Object o = d.newInstance(); Byte b = 1; Character c = 2; Short s = 3; Integer i = 4; Long j = 5L; Object o1 = b; Object o2 = c; Object o3 = s; Object o4 = i; Object o5 = j; Object o6 = "6"; Object result = m.invoke(null, o, b, c, s, i, j, o1, o2, o3, o4, o5, o6); if (expected != null) { System.out.println("FAIL, Expected " + expected.getName() + " wrapped in InvocationTargetException, but nothing was thrown"); lt.add(new Error("Exception " + expected.getName() + " was not thrown")); } else { System.out.println("PASS, saw expected return."); } } catch (InvocationTargetException e) { Throwable th = e.getCause(); th.printStackTrace(System.out); if (expected != null) { if (expected.isInstance(th)) { System.out.println("PASS, saw expected exception (" + expected.getName() + ")."); } else { System.out.println("FAIL, Expected " + expected.getName() + " wrapped in InvocationTargetException, saw " + th); lt.add(th); } } else { System.out.println("FAIL, expected no exception, saw " + th); lt.add(th); } } System.out.println(); } /** * This tests a peculiar idiom for tickling the bug on older VMs that lack * methodhandles. The bug (if not fixed) acts in the following way: * * When a broken receiver is passed to the first execution of an invokeinterface * bytecode, the illegal access is detected before the effects of resolution are * cached for later use, and so repeated calls with a broken receiver will always * throw the correct error. * * If, however, a good receiver is passed to the invokeinterface, the effects of * resolution will be successfully cached. A subsequent execution with a broken * receiver will reuse the cached information, skip the detailed resolution work, * and instead encounter a null pointer. By convention, that is the encoding for a * missing abstract method, and an AbstractMethodError is thrown -- not the expected * IllegalAccessError. * * @param t2 Test invocation class * @param d2 Test receiver class * @param expected expected exception type * @param lt list of unexpected throwables seen */ private static void badGoodBadGood(Class<?> t2, Class<?> d2, Class<?> expected, List<Throwable> lt) throws Throwable { System.out.println(" Error input 1st time"); invokeTest(t2, d2, expected, lt); System.out.println(" Good input (instance of Dok)"); invokeTest(t2, Dok.class, null, lt); System.out.println(" Error input 2nd time"); invokeTest(t2, d2, expected, lt); System.out.println(" Good input (instance of Dok)"); invokeTest(t2, Dok.class, null, lt); } private static void badGoodBadGoodMany(Class<?> t2, Class<?> d2, Class<?> expected, List<Throwable> lt) throws Throwable { System.out.println(" Error input 1st time"); invokeTestMany(t2, d2, expected, lt); System.out.println(" Good input (instance of Dok)"); invokeTestMany(t2, Dok.class, null, lt); System.out.println(" Error input 2nd time"); invokeTestMany(t2, d2, expected, lt); System.out.println(" Good input (instance of Dok)"); invokeTestMany(t2, Dok.class, null, lt); } }