changeset 8281:d90928a89af5

8022701: Accessibility checking: InvocationTargetException is thrown instead of IllegalAccessError Summary: Inserted code to convert specific exceptions, case-by-case, plus a test. Reviewed-by: jrose, twisti
author drchase
date Fri, 27 Sep 2013 13:32:32 -0400
parents 84e7f6685319
children 3fca37c636be
files src/share/classes/java/lang/invoke/MethodHandleNatives.java test/java/lang/invoke/8022701/BogoLoader.java test/java/lang/invoke/8022701/InvokeSeveralWays.java test/java/lang/invoke/8022701/Invoker.java test/java/lang/invoke/8022701/MHIllegalAccess.java test/java/lang/invoke/8022701/MethodSupplier.java
diffstat 6 files changed, 486 insertions(+), 3 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/lang/invoke/MethodHandleNatives.java	Tue Oct 01 15:40:34 2013 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandleNatives.java	Fri Sep 27 13:32:32 2013 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2013, 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
@@ -440,14 +440,34 @@
             Lookup lookup = IMPL_LOOKUP.in(callerClass);
             assert(refKindIsValid(refKind));
             return lookup.linkMethodHandleConstant((byte) refKind, defc, name, type);
+        } catch (IllegalAccessException ex) {
+            Error err = new IllegalAccessError(ex.getMessage());
+            throw initCauseFrom(err, ex);
+        } catch (NoSuchMethodException ex) {
+            Error err = new NoSuchMethodError(ex.getMessage());
+            throw initCauseFrom(err, ex);
+        } catch (NoSuchFieldException ex) {
+            Error err = new NoSuchFieldError(ex.getMessage());
+            throw initCauseFrom(err, ex);
         } catch (ReflectiveOperationException ex) {
             Error err = new IncompatibleClassChangeError();
-            err.initCause(ex);
-            throw err;
+            throw initCauseFrom(err, ex);
         }
     }
 
     /**
+     * Use best possible cause for err.initCause(), substituting the
+     * cause for err itself if the cause has the same (or better) type.
+     */
+    static private Error initCauseFrom(Error err, Exception ex) {
+        Throwable th = ex.getCause();
+        if (err.getClass().isInstance(th))
+           return (Error) th;
+        err.initCause(th == null ? ex : th);
+        return err;
+    }
+
+    /**
      * Is this method a caller-sensitive method?
      * I.e., does it call Reflection.getCallerClass or a similer method
      * to ask about the identity of its caller?
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/8022701/BogoLoader.java	Fri Sep 27 13:32:32 2013 -0400
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ */
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+import java.util.Vector;
+import jdk.internal.org.objectweb.asm.*;
+
+public class BogoLoader extends ClassLoader {
+
+    static interface VisitorMaker {
+    ClassVisitor make(ClassVisitor visitor);
+    }
+
+
+    /**
+     * Use this property to verify that the desired classloading is happening.
+     */
+    private final boolean verbose = Boolean.getBoolean("bogoloader.verbose");
+    /**
+     * Use this property to disable replacement for testing purposes.
+     */
+    private final boolean noReplace = Boolean.getBoolean("bogoloader.noreplace");
+
+    /**
+     * Set of class names that should be loaded with this loader.
+     * Others are loaded with the system class loader, except for those
+     * that are transformed.
+     */
+    private Set<String> nonSystem;
+
+    /**
+     * Map from class names to a bytecode transformer factory.
+     */
+    private Map<String, VisitorMaker> replaced;
+
+    /**
+     * Keep track (not terribly efficiently) of which classes have already
+     * been loaded by this class loader.
+     */
+    private final Vector<String> history = new Vector<String>();
+
+    private boolean useSystemLoader(String name) {
+        return ! nonSystem.contains(name) && ! replaced.containsKey(name);
+    }
+
+    public BogoLoader(Set<String> non_system, Map<String, VisitorMaker> replaced) {
+        super(Thread.currentThread().getContextClassLoader());
+        this.nonSystem = non_system;
+        this.replaced = replaced;
+    }
+
+    private byte[] readResource(String className) throws IOException {
+        return readResource(className, "class");
+    }
+
+    private byte[] readResource(String className, String suffix) throws IOException {
+        // Note to the unwary -- "/" works on Windows, leave it alone.
+        String fileName = className.replace('.', '/') + "." + suffix;
+        InputStream origStream = getResourceAsStream(fileName);
+        if (origStream == null) {
+            throw new IOException("Resource not found : " + fileName);
+        }
+        BufferedInputStream stream = new java.io.BufferedInputStream(origStream);
+        byte[] data = new byte[stream.available()];
+        int how_many = stream.read(data);
+        // Really ought to deal with the corner cases of stream.available()
+        return data;
+    }
+
+    protected byte[] getClass(String name) throws ClassNotFoundException,
+    IOException {
+        return readResource(name, "class");
+    }
+
+    /**
+     * Loads the named class from the system class loader unless
+     * the name appears in either replaced or nonSystem.
+     * nonSystem classes are loaded into this classloader,
+     * and replaced classes get their content from the specified array
+     * of bytes (and are also loaded into this classloader).
+     */
+    protected Class<?> loadClass(String name, boolean resolve)
+            throws ClassNotFoundException {
+        Class<?> clazz;
+
+        if (history.contains(name)) {
+            Class<?> c = this.findLoadedClass(name);
+            return c;
+        }
+        if (useSystemLoader(name)) {
+            clazz = findSystemClass(name);
+            if (verbose) System.err.println("Loading system class " + name);
+        } else {
+            history.add(name);
+            try {
+                if (verbose) {
+                    System.err.println("Loading classloader class " + name);
+                }
+                byte[] classData = getClass(name);;
+                boolean expanded = false;
+                if (!noReplace && replaced.containsKey(name)) {
+                    if (verbose) {
+                        System.err.println("Replacing class " + name);
+                    }
+                    ClassReader cr = new ClassReader(classData);
+                    ClassWriter cw = new ClassWriter(0);
+                    VisitorMaker vm = replaced.get(name);
+                    cr.accept(vm.make(cw), 0);
+                    classData = cw.toByteArray();
+                }
+                clazz = defineClass(name, classData, 0, classData.length);
+            } catch (java.io.EOFException ioe) {
+                throw new ClassNotFoundException(
+                        "IO Exception in reading class : " + name + " ", ioe);
+            } catch (ClassFormatError ioe) {
+                throw new ClassNotFoundException(
+                        "ClassFormatError in reading class file: ", ioe);
+            } catch (IOException ioe) {
+                throw new ClassNotFoundException(
+                        "IO Exception in reading class file: ", ioe);
+            }
+        }
+        if (clazz == null) {
+            throw new ClassNotFoundException(name);
+        }
+        if (resolve) {
+            resolveClass(clazz);
+        }
+        return clazz;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/8022701/InvokeSeveralWays.java	Fri Sep 27 13:32:32 2013 -0400
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ */
+
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Tries various ways of ultimately invoking MethodSupplier.m(),
+ * except that m has been made inaccessible and some exception should be
+ * thrown instead.
+ */
+public class InvokeSeveralWays {
+    public static int test(String args[], Class expected) throws Exception {
+        int failures = 0;
+        try {
+            Class.forName("Invoker").getMethod("invoke").invoke(null);
+            System.out.println("FAIL: No exception throw, probably failed to load modified bytecodes for MethodSupplier");
+            failures++;
+        } catch (InvocationTargetException e) {
+            Throwable c = e.getCause();
+            if (expected.isInstance(c))
+               System.out.println("EXPECTED: " + expected.getName() + ", "+ c);
+            else {
+               failures++;
+               System.out.println("FAIL: Unexpected wrapped exception " + c);
+               e.printStackTrace(System.out);
+            }
+        } catch (Throwable e) {
+            failures++;
+            System.out.println("FAIL: Unexpected exception has been caught " + e);
+            e.printStackTrace(System.out);
+        }
+        System.out.println();
+        try {
+            Class.forName("Invoker").getMethod("invoke2").invoke(null);
+            System.out.println("FAIL: No exception throw, probably failed to load modified bytecodes for MethodSupplier");
+            failures++;
+        } catch (InvocationTargetException e) {
+            Throwable c = e.getCause();
+            if (expected.isInstance(c))
+               System.out.println("EXPECTED: " + expected.getName() + ", "+ c);
+            else {
+               failures++;
+               System.out.println("FAIL: Unexpected wrapped exception " + c);
+               e.printStackTrace(System.out);
+            }
+        } catch (Throwable e) {
+            failures++;
+            System.out.println("FAIL: Unexpected exception has been caught " + e);
+            e.printStackTrace(System.out);
+        }
+        System.out.println();
+        try {
+            Invoker.invoke();
+            System.out.println("FAIL: No exception throw, probably failed to load modified bytecodes for MethodSupplier");
+            failures++;
+        } catch (Throwable e) {
+            if (expected.isInstance(e))
+               System.out.println("EXPECTED: " + expected.getName() + ", "+ e);
+            else {
+            failures++;
+            System.out.println("FAIL: Unexpected exception has been caught " + e);
+            e.printStackTrace(System.out);
+            }
+        }
+        System.out.println();
+        try {
+            Invoker.invoke2();
+            System.out.println("FAIL: No exception throw, probably failed to load modified bytecodes for MethodSupplier");
+            failures++;
+         } catch (Throwable e) {
+            if (expected.isInstance(e))
+               System.out.println("EXPECTED: " + expected.getName() + ", "+ e);
+            else {
+                failures++;
+                System.out.println("FAIL: Unexpected exception has been caught " + e);
+                e.printStackTrace(System.out);
+            }
+        }
+        System.out.println();
+        if (failures > 0) {
+          System.out.println("Saw " + failures + " failures");
+        }
+        return failures;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/8022701/Invoker.java	Fri Sep 27 13:32:32 2013 -0400
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ */
+
+public class Invoker {
+    /**
+     * Use a method handle to invoke m.
+     */
+    public static void invoke() {
+        MyFunctionalInterface fi = null;
+        fi = new MethodSupplier()::<Integer, String, Long>m;
+        fi.invokeMethodReference();
+    }
+    /**
+     * Invoke m directly.
+     */
+    public static void invoke2() {
+        MethodSupplier ms = new MethodSupplier();
+        ms.m();
+    }
+}
+
+interface MyFunctionalInterface {
+       void invokeMethodReference();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/8022701/MHIllegalAccess.java	Fri Sep 27 13:32:32 2013 -0400
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2013, 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 8022701
+ * @summary Illegal access exceptions via methodhandle invocations threw wrong error.
+ *
+ * @compile -XDignore.symbol.file BogoLoader.java InvokeSeveralWays.java MHIllegalAccess.java MethodSupplier.java
+ * @run main/othervm MHIllegalAccess
+ */
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.HashMap;
+import java.util.HashSet;
+import jdk.internal.org.objectweb.asm.ClassWriter;
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.internal.org.objectweb.asm.ClassVisitor;
+import jdk.internal.org.objectweb.asm.Opcodes;
+
+public class MHIllegalAccess implements Opcodes {
+
+   public static void main(String args[]) throws Throwable  {
+      System.out.println("Classpath is " + System.getProperty("java.class.path"));
+      System.out.println();
+
+      /**
+       * Make method m be private to provoke an IllegalAccessError.
+       */
+      BogoLoader.VisitorMaker privatize = new BogoLoader.VisitorMaker() {
+           public ClassVisitor make(ClassVisitor cv) {
+               return new ClassVisitor(Opcodes.ASM5, cv) {
+                   public MethodVisitor visitMethod(int access, String name, String desc,
+                           String signature, String[] exceptions) {
+                       if (name.equals("m"))
+                           access = (access | ACC_PRIVATE) & ~ (ACC_PUBLIC | ACC_PROTECTED);
+                           return super.visitMethod(access, name, desc, signature, exceptions);
+                   }
+               };
+           }
+       };
+
+     /**
+       * Rename method m as nemo to provoke a NoSuchMethodError.
+       */
+     BogoLoader.VisitorMaker changeName = new BogoLoader.VisitorMaker() {
+           public ClassVisitor make(ClassVisitor cv) {
+               return new ClassVisitor(Opcodes.ASM5, cv) {
+                   public MethodVisitor visitMethod(int access, String name, String desc,
+                           String signature, String[] exceptions) {
+                       if (name.equals("m"))
+                           name = "nemo";
+                           return super.visitMethod(access, name, desc, signature, exceptions);
+                   }
+               };
+           }
+       };
+
+      int failures = 0;
+      failures += testOneError(privatize, args, IllegalAccessError.class);
+      failures += testOneError(changeName, args, NoSuchMethodError.class);
+      if (failures > 0) {
+          System.out.println("Saw " + failures + " failures, see standard out for details");
+          throw new Error("FAIL test");
+      }
+   }
+
+   /**
+    *
+    * @param vm VisitorMaker, to be stored in a table and passed to a BogoLoader
+    * @param args A copy of the main args, to be passed on to InvokeSeveralWays.test
+    * @param expected The class of the exception that should be thrown after
+    *                 attempted invocation of MethodSupplier.m.
+    * @throws ClassNotFoundException
+    * @throws Throwable
+    */
+    private static int testOneError(BogoLoader.VisitorMaker vm, String[] args, Class expected) throws ClassNotFoundException, Throwable {
+      HashMap<String, BogoLoader.VisitorMaker> replace = new HashMap<String, BogoLoader.VisitorMaker>();
+      replace.put("MethodSupplier", vm);
+
+      HashSet<String> in_bogus = new HashSet<String>();
+        in_bogus.add("InvokeSeveralWays");
+        in_bogus.add("MyFunctionalInterface");
+        in_bogus.add("Invoker");
+
+        BogoLoader bl = new BogoLoader(in_bogus, replace);
+        Class<?> isw = bl.loadClass("InvokeSeveralWays");
+        Object[] arg_for_args = new Object[2];
+        arg_for_args[0] = args;
+        arg_for_args[1] = expected;
+        try {
+            Object result = isw.getMethod("test", String[].class, Class.class).invoke(null, arg_for_args);
+            return (Integer)result;
+        } catch (InvocationTargetException e) {
+            Throwable th = e.getCause();
+            throw th == null ? e : th;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/lang/invoke/8022701/MethodSupplier.java	Fri Sep 27 13:32:32 2013 -0400
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2013, 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.
+ *
+ */
+
+/*
+ * Note: this class is only used in this form to facilitate compilation of the
+ * rest of the code.  Before execution, the bytecodes are mutilated in a
+ * BogoLoader to either make m be private or have a different name.
+ */
+
+public class MethodSupplier {
+    public void m() {
+        System.out.println("good");
+    }
+}
+