8166974: invokedynamic implementation should not wrap Errors
authorpsandoz
Fri, 14 Oct 2016 14:47:01 -0700
changeset 42074 c069e5e285cb
parent 42073 89e056fd82cc
child 42075 e5b263ac8c2e
child 42076 d6c3ecec1d34
8166974: invokedynamic implementation should not wrap Errors Reviewed-by: dholmes, jrose
hotspot/src/share/vm/classfile/systemDictionary.cpp
hotspot/src/share/vm/interpreter/linkResolver.cpp
hotspot/test/runtime/ConstantPool/TestMethodHandleConstant.java
hotspot/test/runtime/invokedynamic/BootstrapMethodErrorTest.java
--- a/hotspot/src/share/vm/classfile/systemDictionary.cpp	Thu Oct 27 09:42:45 2016 -0500
+++ b/hotspot/src/share/vm/classfile/systemDictionary.cpp	Fri Oct 14 14:47:01 2016 -0700
@@ -2698,7 +2698,7 @@
   }
   assert(arg == npts, "");
 
-  // call java.lang.invoke.MethodHandleNatives::findMethodType(Class rt, Class[] pts) -> MethodType
+  // call java.lang.invoke.MethodHandleNatives::findMethodHandleType(Class rt, Class[] pts) -> MethodType
   JavaCallArguments args(Handle(THREAD, rt()));
   args.push_oop(pts());
   JavaValue result(T_OBJECT);
--- a/hotspot/src/share/vm/interpreter/linkResolver.cpp	Thu Oct 27 09:42:45 2016 -0500
+++ b/hotspot/src/share/vm/interpreter/linkResolver.cpp	Fri Oct 14 14:47:01 2016 -0700
@@ -1627,19 +1627,23 @@
 
 static void wrap_invokedynamic_exception(TRAPS) {
   if (HAS_PENDING_EXCEPTION) {
+    // See the "Linking Exceptions" section for the invokedynamic instruction
+    // in JVMS 6.5.
+    if (PENDING_EXCEPTION->is_a(SystemDictionary::Error_klass())) {
+      // Pass through an Error, including BootstrapMethodError, any other form
+      // of linkage error, or say ThreadDeath/OutOfMemoryError
+      if (TraceMethodHandles) {
+        tty->print_cr("invokedynamic passes through an Error for " INTPTR_FORMAT, p2i((void *)PENDING_EXCEPTION));
+        PENDING_EXCEPTION->print();
+      }
+      return;
+    }
+
+    // Otherwise wrap the exception in a BootstrapMethodError
     if (TraceMethodHandles) {
       tty->print_cr("invokedynamic throws BSME for " INTPTR_FORMAT, p2i((void *)PENDING_EXCEPTION));
       PENDING_EXCEPTION->print();
     }
-    if (PENDING_EXCEPTION->is_a(SystemDictionary::BootstrapMethodError_klass())) {
-      // throw these guys, since they are already wrapped
-      return;
-    }
-    if (!PENDING_EXCEPTION->is_a(SystemDictionary::LinkageError_klass())) {
-      // intercept only LinkageErrors which might have failed to wrap
-      return;
-    }
-    // See the "Linking Exceptions" section for the invokedynamic instruction in the JVMS.
     Handle nested_exception(THREAD, PENDING_EXCEPTION);
     CLEAR_PENDING_EXCEPTION;
     THROW_CAUSE(vmSymbols::java_lang_BootstrapMethodError(), nested_exception)
--- a/hotspot/test/runtime/ConstantPool/TestMethodHandleConstant.java	Thu Oct 27 09:42:45 2016 -0500
+++ b/hotspot/test/runtime/ConstantPool/TestMethodHandleConstant.java	Fri Oct 14 14:47:01 2016 -0700
@@ -23,7 +23,7 @@
 
 /*
  * @test
- * @bug 8159470
+ * @bug 8159470 8166974
  * @summary Test that MethodHandle constants are checked
  * @modules java.base/jdk.internal.misc
  * @compile WithConfiguration.jcod
@@ -33,12 +33,13 @@
 
     public static void main(String[] args) {
         try {
-          // This interface has bad constant pool entry for MethodHandle -> Method
-          String URI_DEFAULT
-            = WithConfiguration.autoDetect().getLocation();
-          throw new RuntimeException("FAILED, ICCE not thrown");
-        } catch (BootstrapMethodError icce) {
-          System.out.println("PASSED, expecting ICCE" + icce.getMessage());
+            // This interface has bad constant pool entry for MethodHandle -> Method
+            String URI_DEFAULT
+                    = WithConfiguration.autoDetect().getLocation();
+            throw new RuntimeException("FAILED, IncompatibleClassChangeError not thrown");
+        }
+        catch (IncompatibleClassChangeError icce) {
+            System.out.println("PASSED, expecting IncompatibleClassChangeError" + icce.getMessage());
         }
     }
 }
--- a/hotspot/test/runtime/invokedynamic/BootstrapMethodErrorTest.java	Thu Oct 27 09:42:45 2016 -0500
+++ b/hotspot/test/runtime/invokedynamic/BootstrapMethodErrorTest.java	Fri Oct 14 14:47:01 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2016, 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
@@ -23,93 +23,296 @@
 
 /*
  * @test
- * @bug 8051045
- * @summary Test that exceptions from invokedynamic are wrapped in BootstrapMethodError
+ * @bug 8051045 8166974
+ * @summary Test exceptions from invokedynamic and the bootstrap method
  * @modules java.base/jdk.internal.org.objectweb.asm
  * @run main BootstrapMethodErrorTest
  */
 
-import java.lang.reflect.Method;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import static java.lang.invoke.MethodHandles.*;
-import static java.lang.invoke.MethodType.*;
-
 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;
 
-public class BootstrapMethodErrorTest extends ClassLoader implements Opcodes {
+import java.lang.invoke.CallSite;
+import java.lang.invoke.ConstantCallSite;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.WrongMethodTypeException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+public class BootstrapMethodErrorTest {
+
+    static abstract class IndyClassloader extends ClassLoader implements Opcodes {
+
+        public IndyClassloader() {
+            super(BootstrapMethodErrorTest.class.getClassLoader());
+        }
+
+        @Override
+        public Class findClass(String name) throws ClassNotFoundException {
+            byte[] b;
+            try {
+                b = loadClassData(name);
+            }
+            catch (Throwable th) {
+                throw new ClassNotFoundException("Loading error", th);
+            }
+            return defineClass(name, b, 0, b.length);
+        }
+
+        static final String BOOTSTRAP_METHOD_CLASS_NAME = "C";
+
+        static final String BOOTSTRAP_METHOD_NAME = "bsm";
+
+        static final String INDY_CALLER_CLASS_NAME = "Exec";
+
+        static final String BOOTSTRAP_METHOD_DESC = MethodType.methodType(
+                Object.class, MethodHandles.Lookup.class, String.class, MethodType.class).
+                toMethodDescriptorString();
+
+        private byte[] loadClassData(String name) throws Exception {
+            ClassWriter cw = new ClassWriter(
+                    ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
+            if (name.equals(BOOTSTRAP_METHOD_CLASS_NAME)) {
+                defineIndyBootstrapMethodClass(cw);
+                return cw.toByteArray();
+            }
+            else if (name.equals("Exec")) {
+                defineIndyCallingClass(cw);
+                return cw.toByteArray();
+            }
+            return null;
+        }
+
+        void defineIndyCallingClass(ClassWriter cw) {
+            cw.visit(52, ACC_SUPER | ACC_PUBLIC, INDY_CALLER_CLASS_NAME, null, "java/lang/Object", null);
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invoke", "()V", null, null);
+            mv.visitCode();
+            Handle h = new Handle(H_INVOKESTATIC,
+                                  BOOTSTRAP_METHOD_CLASS_NAME, BOOTSTRAP_METHOD_NAME,
+                                  BOOTSTRAP_METHOD_DESC, false);
+            mv.visitInvokeDynamicInsn(BOOTSTRAP_METHOD_CLASS_NAME, "()V", h);
+            mv.visitInsn(RETURN);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+            cw.visitEnd();
+        }
 
-  @Override
-  public Class findClass(String name) throws ClassNotFoundException {
-    byte[] b;
-    try {
-      b = loadClassData(name);
-    } catch (Throwable th) {
-      throw new ClassNotFoundException("Loading error", th);
+        void defineIndyBootstrapMethodClass(ClassWriter cw) {
+            cw.visit(52, ACC_SUPER | ACC_PUBLIC,
+                     BOOTSTRAP_METHOD_CLASS_NAME, null, "java/lang/Object", null);
+            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC,
+                                              BOOTSTRAP_METHOD_NAME, BOOTSTRAP_METHOD_DESC, null, null);
+            mv.visitCode();
+            defineIndyBootstrapMethodBody(mv);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        void defineIndyBootstrapMethodBody(MethodVisitor mv) {
+            mv.visitInsn(ACONST_NULL);
+            mv.visitInsn(ARETURN);
+        }
+
+        void invoke() throws Exception {
+            Class.forName(BOOTSTRAP_METHOD_CLASS_NAME, true, this);
+            Class<?> exec = Class.forName(INDY_CALLER_CLASS_NAME, true, this);
+            exec.getMethod("invoke").invoke(null);
+        }
+
+        void test() throws Exception {
+            Class.forName(BOOTSTRAP_METHOD_CLASS_NAME, true, this);
+            Class<?> exec = Class.forName(INDY_CALLER_CLASS_NAME, true, this);
+            try {
+                exec.getMethod("invoke").invoke(null);
+                throw new RuntimeException("Expected InvocationTargetException but no exception at all was thrown");
+            } catch (InvocationTargetException e) {
+                Throwable t = e.getCause();
+                for (Class<? extends Throwable> etc : expectedThrowableClasses()) {
+                    if (!etc.isInstance(t)) {
+                        throw new RuntimeException(
+                                "Expected " + etc.getName() + " but got another exception: "
+                                + t.getClass().getName(),
+                                t);
+                    }
+                    t = t.getCause();
+                }
+            }
+        }
+
+        abstract List<Class<? extends Throwable>> expectedThrowableClasses();
     }
-    return defineClass(name, b, 0, b.length);
-  }
+
+    // Methods called by a bootstrap method
 
-  private byte[] loadClassData(String name) throws Exception {
-    ClassWriter cw = new ClassWriter(0);
-    MethodVisitor mv;
+    public static CallSite getCallSite() {
+        try {
+            MethodHandle mh = MethodHandles.lookup().findStatic(
+                    BootstrapMethodErrorTest.class,
+                    "target",
+                    MethodType.methodType(Object.class, Object.class));
+            return new ConstantCallSite(mh);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+    public static Object target(Object o) {
+        return null;
+    }
+
+    static class TestThrowable extends Throwable {}
+    public static void throwsTestThrowable() throws Throwable {
+        throw new TestThrowable();
+    }
+
+    static class TestError extends Error {}
+    public static void throwsTestError() {
+        throw new TestError();
+    }
 
-    if (name.equals("C")) {
-      cw.visit(52, ACC_SUPER | ACC_PUBLIC, "C", null, "java/lang/Object", null);
-      {
-        mv = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, "m", "()V", null, null);
-        mv.visitCode();
-        mv.visitInsn(RETURN);
-        mv.visitMaxs(0, 1);
-        mv.visitEnd();
-      }
-      cw.visitEnd();
-      return cw.toByteArray();
-    } else if (name.equals("Exec")) {
-      cw.visit(52, ACC_SUPER | ACC_PUBLIC, "Exec", null, "java/lang/Object", null);
-      {
-        mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "invokeRef", "()V", null, null);
-        mv.visitCode();
-        Handle h = new Handle(H_INVOKESTATIC, "C", "m", "()V");
-        mv.visitInvokeDynamicInsn("C", "()V", h);
-        mv.visitInsn(RETURN);
-        mv.visitMaxs(0, 0);
-        mv.visitEnd();
-      }
-      cw.visitEnd();
-      return cw.toByteArray();
+    static class TestRuntimeException extends RuntimeException {}
+    public static void throwsTestRuntimeException() {
+        throw new TestRuntimeException();
+    }
+
+    static class TestCheckedException extends Exception {}
+    public static void throwsTestCheckedException() throws TestCheckedException {
+        throw new TestCheckedException();
+    }
+
+
+    // Test classes
+
+    static class InaccessibleBootstrapMethod extends IndyClassloader {
+
+        void defineIndyBootstrapMethodClass(ClassWriter cw) {
+            cw.visit(52, ACC_SUPER | ACC_PUBLIC,
+                     BOOTSTRAP_METHOD_CLASS_NAME, null, "java/lang/Object", null);
+            // Bootstrap method is declared to be private
+            MethodVisitor mv = cw.visitMethod(ACC_PRIVATE | ACC_STATIC,
+                                              BOOTSTRAP_METHOD_NAME, BOOTSTRAP_METHOD_DESC, null, null);
+            mv.visitCode();
+            defineIndyBootstrapMethodBody(mv);
+            mv.visitMaxs(0, 0);
+            mv.visitEnd();
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(IllegalAccessError.class);
+        }
     }
-    return null;
-  }
+
+    static class BootstrapMethodDoesNotReturnCallSite extends IndyClassloader {
+
+        void defineIndyBootstrapMethodBody(MethodVisitor mv) {
+            // return null from the bootstrap method,
+            // which cannot be cast to CallSite
+            mv.visitInsn(ACONST_NULL);
+            mv.visitInsn(ARETURN);
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(BootstrapMethodError.class, ClassCastException.class);
+        }
+    }
+
+    static class BootstrapMethodCallSiteHasWrongTarget extends IndyClassloader {
+
+        @Override
+        void defineIndyBootstrapMethodBody(MethodVisitor mv) {
+            // Invoke the method BootstrapMethodErrorTest.getCallSite to obtain
+            // a CallSite instance whose target is different from that of
+            // the indy call site
+            mv.visitMethodInsn(INVOKESTATIC, "BootstrapMethodErrorTest",
+                               "getCallSite", "()Ljava/lang/invoke/CallSite;", false);
+            mv.visitInsn(ARETURN);
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(BootstrapMethodError.class, WrongMethodTypeException.class);
+        }
+    }
+
+    abstract static class BootstrapMethodThrows extends IndyClassloader {
+        final String methodName;
 
-  public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException {
-    new BootstrapMethodErrorTest().test();
-  }
+        public BootstrapMethodThrows(Class<? extends Throwable> t) {
+            this.methodName = "throws" + t.getSimpleName();
+        }
+
+        @Override
+        void defineIndyBootstrapMethodBody(MethodVisitor mv) {
+            // Invoke the method whose name is methodName which will throw
+            // an exception
+            mv.visitMethodInsn(INVOKESTATIC, "BootstrapMethodErrorTest",
+                               methodName, "()V", false);
+            mv.visitInsn(ACONST_NULL);
+            mv.visitInsn(ARETURN);
+        }
+    }
 
-  public void test() throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException {
-    Class.forName("C", true, this);
-    Class<?> exec = Class.forName("Exec", true, this);
+    static class BootstrapMethodThrowsThrowable extends BootstrapMethodThrows {
+
+        public BootstrapMethodThrowsThrowable() {
+            super(TestThrowable.class);
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(BootstrapMethodError.class, TestThrowable.class);
+        }
+    }
+
+    static class BootstrapMethodThrowsError extends BootstrapMethodThrows {
+
+        public BootstrapMethodThrowsError() {
+            super(TestError.class);
+        }
 
-    try {
-      exec.getMethod("invokeRef").invoke(null);
-    } catch (Throwable e) {
-      Throwable c = e.getCause();
-      if (c == null) {
-        throw new RuntimeException(
-            "Expected BootstrapMethodError wrapped in an InvocationTargetException but it wasn't wrapped", e);
-      } else if (c instanceof BootstrapMethodError) {
-        // Only way to pass test, all else should throw
-        return;
-      } else {
-        throw new RuntimeException(
-            "Expected BootstrapMethodError but got another Error: "
-            + c.getClass().getName(),
-            c);
-      }
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(TestError.class);
+        }
+    }
+
+    static class BootstrapMethodThrowsRuntimeException extends BootstrapMethodThrows {
+
+        public BootstrapMethodThrowsRuntimeException() {
+            super(TestRuntimeException.class);
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(BootstrapMethodError.class, TestRuntimeException.class);
+        }
     }
-    throw new RuntimeException("Expected BootstrapMethodError but no Error at all was thrown");
-  }
+
+    static class BootstrapMethodThrowsCheckedException extends BootstrapMethodThrows {
+
+        public BootstrapMethodThrowsCheckedException() {
+            super(TestCheckedException.class);
+        }
+
+        @Override
+        List<Class<? extends Throwable>> expectedThrowableClasses() {
+            return List.of(BootstrapMethodError.class, TestCheckedException.class);
+        }
+    }
+
+
+    public static void main(String[] args) throws Exception {
+        new InaccessibleBootstrapMethod().test();
+        new BootstrapMethodDoesNotReturnCallSite().test();
+        new BootstrapMethodCallSiteHasWrongTarget().test();
+        new BootstrapMethodThrowsThrowable().test();
+        new BootstrapMethodThrowsError().test();
+        new BootstrapMethodThrowsRuntimeException().test();
+        new BootstrapMethodThrowsCheckedException().test();
+    }
 }