diff -r ba63dd5d7b20 -r e8932d2fda2c hotspot/test/compiler/jsr292/methodHandleExceptions/TestAMEnotNPE.java --- a/hotspot/test/compiler/jsr292/methodHandleExceptions/TestAMEnotNPE.java Fri Nov 22 13:42:46 2013 -0800 +++ b/hotspot/test/compiler/jsr292/methodHandleExceptions/TestAMEnotNPE.java Tue Nov 26 18:16:04 2013 -0500 @@ -21,50 +21,127 @@ * questions. * */ - import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; 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 p.Dok; /** - * @test - * @bug 8025260 - * @summary Ensure that AbstractMethodError is thrown, not NullPointerException, through MethodHandles::jump_from_method_handle code path + * @test @bug 8025260 8016839 + * @summary Ensure that AbstractMethodError and IllegalAccessError are thrown appropriately, not NullPointerException + * + * @compile -XDignore.symbol.file TestAMEnotNPE.java ByteClassLoader.java p/C.java p/Dok.java p/E.java p/F.java p/I.java p/Tdirect.java p/Treflect.java * - * @compile -XDignore.symbol.file ByteClassLoader.java I.java C.java TestAMEnotNPE.java * @run main/othervm TestAMEnotNPE + * @run main/othervm -Xint TestAMEnotNPE + * @run main/othervm -Xcomp TestAMEnotNPE */ - public class TestAMEnotNPE implements Opcodes { + static boolean writeJarFiles = false; + static boolean readJarFiles = false; + /** - * 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. + * 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 lt = new ArrayList(); + + 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(); - class D extends C { - D() { super(); } - // does not define m - } + 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); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; - cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "D", null, "C", null); + cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "p/D", null, "p/C", null); { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); - mv.visitMethodInsn(INVOKESPECIAL, "C", "", "()V"); + mv.visitMethodInsn(INVOKESPECIAL, "p/C", "", "()V"); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); @@ -74,70 +151,346 @@ 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); + } /** - * The bytecodes for an invokeExact of a particular methodHandle, I.m, invoked on a D + * 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"}; - class T { - T() { super(); } // boring constructor - int test() { - MethodHandle mh = `I.m():int`; - D d = new D(); - return mh.invokeExact(d); // Should explode here, AbstractMethodError - } + cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, d_name, null, sub_what, interfaces); + { + mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); + mv.visitCode(); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, sub_what, "", "()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); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES + | ClassWriter.COMPUTE_MAXS); MethodVisitor mv; - cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "T", null, "java/lang/Object", null); + cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "p/T", null, "java/lang/Object", null); { mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null); mv.visitCode(); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V"); mv.visitInsn(RETURN); - mv.visitMaxs(0,0); + 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", "()I", null, null); + 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, "I", "m", "()I")); - mv.visitTypeInsn(NEW, "D"); - mv.visitInsn(DUP); - mv.visitMethodInsn(INVOKESPECIAL, "D", "", "()V"); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", "(LI;)I"); + 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.visitMaxs(0, 0); mv.visitEnd(); } cw.visitEnd(); return cw.toByteArray(); } - public static void main(String args[] ) throws Throwable { - ByteClassLoader bcl = new ByteClassLoader(); - Class d = bcl.loadBytes("D", bytesForD()); - Class t = bcl.loadBytes("T", bytesForT()); + private static void tryAndCheckThrown( + List 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 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 { - Object result = t.getMethod("test").invoke(null); - System.out.println("Expected AbstractMethodError wrapped in InvocationTargetException, saw no exception"); - throw new Error("Missing expected exception"); + 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 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(); - if (th instanceof AbstractMethodError) { - th.printStackTrace(System.out); - System.out.println("PASS, saw expected exception (AbstractMethodError, wrapped in InvocationTargetException)."); + 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("Expected AbstractMethodError wrapped in InvocationTargetException, saw " + th); - throw th; + 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 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 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 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 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); } }