changeset 5480:974c98b731ae

7196190: Improve method of handling MethodHandles Summary: Bind callers to caller-sensitive methods. Reviewed-by: twisti, jjh, vlivanov, ahgross
author jrose
date Wed, 19 Sep 2012 18:37:12 -0700
parents 985b53122cf8
children 7302c386ca9c
files src/share/classes/java/lang/invoke/MethodHandleImpl.java src/share/classes/java/lang/invoke/MethodHandleNatives.java src/share/classes/java/lang/invoke/MethodHandleStatics.java src/share/classes/java/lang/invoke/MethodHandles.java src/share/classes/sun/invoke/anon/AnonymousClassLoader.java
diffstat 5 files changed, 297 insertions(+), 75 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java	Wed Sep 19 12:21:24 2012 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java	Wed Sep 19 18:37:12 2012 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2012, 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
@@ -25,7 +25,6 @@
 
 package java.lang.invoke;
 
-import sun.invoke.util.VerifyType;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.ArrayList;
@@ -35,6 +34,7 @@
 import java.util.List;
 import sun.invoke.empty.Empty;
 import sun.invoke.util.ValueConversions;
+import sun.invoke.util.VerifyType;
 import sun.invoke.util.Wrapper;
 import sun.misc.Unsafe;
 import static java.lang.invoke.MethodHandleStatics.*;
@@ -1258,4 +1258,169 @@
         return THROW_EXCEPTION;
     }
     static <T extends Throwable> Empty throwException(T t) throws T { throw t; }
+
+    /**
+     * Create an alias for the method handle which, when called,
+     * appears to be called from the same class loader and protection domain
+     * as hostClass.
+     * This is an expensive no-op unless the method which is called
+     * is sensitive to its caller.  A small number of system methods
+     * are in this category, including Class.forName and Method.invoke.
+     */
+    static
+    MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) {
+        return BindCaller.bindCaller(mh, hostClass);
+    }
+
+    // Put the whole mess into its own nested class.
+    // That way we can lazily load the code and set up the constants.
+    private static class BindCaller {
+        static
+        MethodHandle bindCaller(MethodHandle mh, Class<?> hostClass) {
+            // Do not use this function to inject calls into system classes.
+            if (hostClass == null) {
+                hostClass = C_Trampoline;
+            } else if (hostClass.isArray() ||
+                       hostClass.isPrimitive() ||
+                       hostClass.getName().startsWith("java.") ||
+                       hostClass.getName().startsWith("sun.")) {
+                throw new InternalError();  // does not happen, and should not anyway
+            }
+            // For simplicity, convert mh to a varargs-like method.
+            MethodHandle vamh = prepareForInvoker(mh);
+            // Cache the result of makeInjectedInvoker once per argument class.
+            MethodHandle bccInvoker = CV_makeInjectedInvoker.get(hostClass);
+            return restoreToType(bccInvoker.bindTo(vamh), mh.type());
+        }
+
+        // This class ("Trampoline") is known to be inside a dead-end class loader.
+        // Inject all doubtful calls into this class.
+        private static Class<?> C_Trampoline;
+        static {
+            Class<?> tramp = null;
+            try {
+                final int FRAME_COUNT_ARG = 1;  // [0] Reflection [1] Trampoline
+                java.lang.reflect.Method gcc = sun.reflect.Reflection.class.getMethod("getCallerClass", int.class);
+                tramp = (Class<?>) sun.reflect.misc.MethodUtil.invoke(gcc, null, new Object[]{ FRAME_COUNT_ARG });
+                if (tramp.getClassLoader() == BindCaller.class.getClassLoader())
+                    throw new RuntimeException(tramp.getName()+" class loader");
+            } catch (Throwable ex) {
+                throw new InternalError(ex.toString());
+            }
+            C_Trampoline = tramp;
+        }
+
+        private static final Unsafe UNSAFE = Unsafe.getUnsafe();
+
+        private static MethodHandle makeInjectedInvoker(Class<?> hostClass) {
+            Class<?> bcc = UNSAFE.defineAnonymousClass(hostClass, T_BYTES, null);
+            if (hostClass.getClassLoader() != bcc.getClassLoader())
+                throw new InternalError(hostClass.getName()+" (CL)");
+            try {
+                if (hostClass.getProtectionDomain() != bcc.getProtectionDomain())
+                    throw new InternalError(hostClass.getName()+" (PD)");
+            } catch (SecurityException ex) {
+                // Self-check was blocked by security manager.  This is OK.
+                // In fact the whole try body could be turned into an assertion.
+            }
+            try {
+                MethodHandle init = IMPL_LOOKUP.findStatic(bcc, "init", MethodType.methodType(void.class));
+                init.invokeExact();  // force initialization of the class
+            } catch (Throwable ex) {
+                throw uncaughtException(ex);
+            }
+            MethodHandle bccInvoker;
+            try {
+                MethodType invokerMT = MethodType.methodType(Object.class, MethodHandle.class, Object[].class);
+                bccInvoker = IMPL_LOOKUP.findStatic(bcc, "invoke_V", invokerMT);
+            } catch (ReflectiveOperationException ex) {
+                throw uncaughtException(ex);
+            }
+            // Test the invoker, to ensure that it really injects into the right place.
+            try {
+                MethodHandle vamh = prepareForInvoker(MH_checkCallerClass);
+                Object ok = bccInvoker.invokeExact(vamh, new Object[]{hostClass, bcc});
+            } catch (Throwable ex) {
+                throw new InternalError(ex.toString());
+            }
+            return bccInvoker;
+        }
+        private static ClassValue<MethodHandle> CV_makeInjectedInvoker = new ClassValue<MethodHandle>() {
+            @Override protected MethodHandle computeValue(Class<?> hostClass) {
+                return makeInjectedInvoker(hostClass);
+            }
+        };
+
+        // Adapt mh so that it can be called directly from an injected invoker:
+        private static MethodHandle prepareForInvoker(MethodHandle mh) {
+            mh = mh.asFixedArity();
+            MethodType mt = mh.type();
+            int arity = mt.parameterCount();
+            MethodHandle vamh = mh.asType(mt.generic());
+            vamh = vamh.asSpreader(Object[].class, arity);
+            return vamh;
+        }
+
+        // Undo the adapter effect of prepareForInvoker:
+        private static MethodHandle restoreToType(MethodHandle vamh, MethodType type) {
+            return vamh.asCollector(Object[].class, type.parameterCount()).asType(type);
+        }
+
+        private static final MethodHandle MH_checkCallerClass;
+        static {
+            final Class<?> THIS_CLASS = BindCaller.class;
+            assert(checkCallerClass(THIS_CLASS, THIS_CLASS));
+            try {
+                MH_checkCallerClass = IMPL_LOOKUP
+                    .findStatic(THIS_CLASS, "checkCallerClass",
+                                MethodType.methodType(boolean.class, Class.class, Class.class));
+                assert((boolean) MH_checkCallerClass.invokeExact(THIS_CLASS, THIS_CLASS));
+            } catch (Throwable ex) {
+                throw new InternalError(ex.toString());
+            }
+        }
+
+        private static boolean checkCallerClass(Class<?> expected, Class<?> expected2) {
+            final int FRAME_COUNT_ARG = 2;  // [0] Reflection [1] BindCaller [2] Expected
+            Class<?> actual = sun.reflect.Reflection.getCallerClass(FRAME_COUNT_ARG);
+            if (actual != expected && actual != expected2)
+                throw new InternalError("found "+actual.getName()+", expected "+expected.getName()
+                                        +(expected == expected2 ? "" : ", or else "+expected2.getName()));
+            return true;
+        }
+
+        private static final byte[] T_BYTES;
+        static {
+            final Object[] values = {null};
+            AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    public Void run() {
+                        try {
+                            Class<T> tClass = T.class;
+                            String tName = tClass.getName();
+                            String tResource = tName.substring(tName.lastIndexOf('.')+1)+".class";
+                            java.net.URLConnection uconn = tClass.getResource(tResource).openConnection();
+                            int len = uconn.getContentLength();
+                            byte[] bytes = new byte[len];
+                            try (java.io.InputStream str = uconn.getInputStream()) {
+                                int nr = str.read(bytes);
+                                if (nr != len)  throw new java.io.IOException(tResource);
+                            }
+                            values[0] = bytes;
+                        } catch (java.io.IOException ex) {
+                            throw new InternalError(ex.toString());
+                        }
+                        return null;
+                    }
+                });
+            T_BYTES = (byte[]) values[0];
+        }
+
+        // The following class is used as a template for Unsafe.defineAnonymousClass:
+        private static class T {
+            static void init() { }  // side effect: initializes this class
+            static Object invoke_V(MethodHandle vamh, Object[] args) throws Throwable {
+                return vamh.invokeExact(args);
+            }
+        }
+    }
 }
--- a/src/share/classes/java/lang/invoke/MethodHandleNatives.java	Wed Sep 19 12:21:24 2012 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandleNatives.java	Wed Sep 19 18:37:12 2012 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2012, 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
@@ -401,4 +401,101 @@
         assert(!HAVE_RICOCHET_FRAMES) : "this code should not be executed if `-XX:+UseRicochetFrames is enabled";
         return true;
     }
+
+    /**
+     * 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?
+     */
+    // FIXME: Replace this pattern match by an annotation @sun.reflect.CallerSensitive.
+    static boolean isCallerSensitive(MemberName mem) {
+        assert(mem.isInvocable());
+        Class<?> defc = mem.getDeclaringClass();
+        switch (mem.getName()) {
+        case "doPrivileged":
+            return defc == java.security.AccessController.class;
+        case "getUnsafe":
+            return defc == sun.misc.Unsafe.class;
+        case "lookup":
+            return defc == java.lang.invoke.MethodHandles.class;
+        case "invoke":
+            return defc == java.lang.reflect.Method.class;
+        case "get":
+        case "getBoolean":
+        case "getByte":
+        case "getChar":
+        case "getShort":
+        case "getInt":
+        case "getLong":
+        case "getFloat":
+        case "getDouble":
+        case "set":
+        case "setBoolean":
+        case "setByte":
+        case "setChar":
+        case "setShort":
+        case "setInt":
+        case "setLong":
+        case "setFloat":
+        case "setDouble":
+            return defc == java.lang.reflect.Field.class;
+        case "newInstance":
+            if (defc == java.lang.reflect.Constructor.class)  return true;
+            if (defc == java.lang.Class.class)  return true;
+            break;
+        case "forName":
+        case "getClassLoader":
+        case "getClasses":
+        case "getFields":
+        case "getMethods":
+        case "getConstructors":
+        case "getDeclaredClasses":
+        case "getDeclaredFields":
+        case "getDeclaredMethods":
+        case "getDeclaredConstructors":
+        case "getField":
+        case "getMethod":
+        case "getConstructor":
+        case "getDeclaredField":
+        case "getDeclaredMethod":
+        case "getDeclaredConstructor":
+            return defc == java.lang.Class.class;
+        case "getConnection":
+        case "getDriver":
+        case "getDrivers":
+        case "deregisterDriver":
+            return defc == java.sql.DriverManager.class;
+        case "newUpdater":
+            if (defc == java.util.concurrent.atomic.AtomicIntegerFieldUpdater.class)  return true;
+            if (defc == java.util.concurrent.atomic.AtomicLongFieldUpdater.class)  return true;
+            if (defc == java.util.concurrent.atomic.AtomicReferenceFieldUpdater.class)  return true;
+            break;
+        case "getContextClassLoader":
+            return defc == java.lang.Thread.class;
+        case "getPackage":
+        case "getPackages":
+            return defc == java.lang.Package.class;
+        case "getParent":
+        case "getSystemClassLoader":
+            return defc == java.lang.ClassLoader.class;
+        case "load":
+        case "loadLibrary":
+            if (defc == java.lang.Runtime.class)  return true;
+            if (defc == java.lang.System.class)  return true;
+            break;
+        case "getCallerClass":
+            if (defc == sun.reflect.Reflection.class)  return true;
+            if (defc == java.lang.System.class)  return true;
+            break;
+        case "getCallerClassLoader":
+            return defc == java.lang.ClassLoader.class;
+        case "getProxyClass":
+        case "newProxyInstance":
+            return defc == java.lang.reflect.Proxy.class;
+        case "getBundle":
+        case "clearCache":
+            return defc == java.util.ResourceBundle.class;
+        }
+        return false;
+    }
 }
--- a/src/share/classes/java/lang/invoke/MethodHandleStatics.java	Wed Sep 19 12:21:24 2012 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandleStatics.java	Wed Sep 19 18:37:12 2012 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2012, 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
@@ -107,7 +107,7 @@
     /*non-public*/ static RuntimeException newIllegalArgumentException(String message, Object obj, Object obj2) {
         return new IllegalArgumentException(message(message, obj, obj2));
     }
-    /*non-public*/ static Error uncaughtException(Exception ex) {
+    /*non-public*/ static Error uncaughtException(Throwable ex) {
         Error err = new InternalError("uncaught exception");
         err.initCause(ex);
         return err;
--- a/src/share/classes/java/lang/invoke/MethodHandles.java	Wed Sep 19 12:21:24 2012 -0700
+++ b/src/share/classes/java/lang/invoke/MethodHandles.java	Wed Sep 19 18:37:12 2012 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2012, 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
@@ -589,7 +589,9 @@
         private
         MethodHandle accessStatic(Class<?> refc, MemberName method) throws IllegalAccessException {
             checkMethod(refc, method, true);
-            return MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
+            MethodHandle mh = MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
+            mh = maybeBindCaller(method, mh);
+            return mh;
         }
         private
         MethodHandle resolveStatic(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
@@ -647,6 +649,7 @@
         private MethodHandle accessVirtual(Class<?> refc, MemberName method) throws IllegalAccessException {
             checkMethod(refc, method, false);
             MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull());
+            mh = maybeBindCaller(method, mh);
             return restrictProtectedReceiver(method, mh);
         }
 
@@ -687,6 +690,7 @@
             checkAccess(refc, ctor);
             MethodHandle rawMH = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull());
             MethodHandle allocMH = MethodHandleImpl.makeAllocator(rawMH);
+            assert(!MethodHandleNatives.isCallerSensitive(ctor));  // maybeBindCaller not relevant here
             return fixVarargs(allocMH, rawMH);
         }
         private MethodHandle resolveConstructor(Class<?> refc, MethodType type) throws NoSuchMethodException, IllegalAccessException {
@@ -755,6 +759,7 @@
                                            Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
             checkMethod(refc, method, false);
             MethodHandle mh = MethodHandleImpl.findMethod(method, false, specialCaller);
+            mh = maybeBindCaller(method, mh);
             return restrictReceiver(method, mh, specialCaller);
         }
         private MethodHandle resolveSpecial(Class<?> refc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
@@ -922,6 +927,8 @@
             checkSecurityManager(refc, method);  // stack walk magic: do not refactor
             checkMethod(refc, method, false);
             MethodHandle dmh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull());
+            MethodHandle bcmh = maybeBindCaller(method, dmh);
+            if (bcmh != dmh)  return fixVarargs(bcmh.bindTo(receiver), dmh);
             MethodHandle bmh = MethodHandleImpl.bindReceiver(dmh, receiver);
             if (bmh == null)
                 throw method.makeAccessException("no access", this);
@@ -956,6 +963,7 @@
                 return MethodHandleImpl.findMethod(method, true, /*no lookupClass*/ null);
             checkMethod(method.getDeclaringClass(), method, method.isStatic());
             MethodHandle mh = MethodHandleImpl.findMethod(method, true, lookupClassOrNull());
+            mh = maybeBindCaller(method, mh);
             return restrictProtectedReceiver(method, mh);
         }
 
@@ -987,6 +995,7 @@
             // ignore m.isAccessible:  this is a new kind of access
             checkMethod(m.getDeclaringClass(), method, false);
             MethodHandle mh = MethodHandleImpl.findMethod(method, false, lookupClassOrNull());
+            mh = maybeBindCaller(method, mh);
             return restrictReceiver(method, mh, specialCaller);
         }
 
@@ -1021,6 +1030,7 @@
                 checkAccess(c.getDeclaringClass(), ctor);
                 rawCtor = MethodHandleImpl.findMethod(ctor, false, lookupClassOrNull());
             }
+            assert(!MethodHandleNatives.isCallerSensitive(ctor));  // maybeBindCaller not relevant here
             MethodHandle allocator = MethodHandleImpl.makeAllocator(rawCtor);
             return fixVarargs(allocator, rawCtor);
         }
@@ -1232,6 +1242,16 @@
             MethodHandle narrowMH = MethodHandleImpl.convertArguments(mh, narrowType, rawType, 0);
             return fixVarargs(narrowMH, mh);
         }
+        private MethodHandle maybeBindCaller(MemberName method, MethodHandle mh) throws IllegalAccessException {
+            if (allowedModes == TRUSTED || !MethodHandleNatives.isCallerSensitive(method))
+                return mh;
+            Class<?> hostClass = lookupClass;
+            if ((allowedModes & PRIVATE) == 0)  // caller must use full-power lookup
+                hostClass = null;
+            MethodHandle cbmh = MethodHandleImpl.bindCaller(mh, hostClass);
+            cbmh = fixVarargs(cbmh, mh);  // in JDK 7 version, varargs happens earlier and must be repaired
+            return cbmh;
+        }
 
         MethodHandle makeAccessor(Class<?> refc, MemberName field,
                                   boolean trusted, boolean isSetter,
--- a/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java	Wed Sep 19 12:21:24 2012 -0700
+++ b/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java	Wed Sep 19 18:37:12 2012 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2008, 2012, 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
@@ -73,74 +73,14 @@
 public class AnonymousClassLoader {
     final Class<?> hostClass;
 
-    // Note: Do not refactor the calls to checkHostClass unless you
-    //       also adjust this constant:
-    private static int CHC_CALLERS = 3;
-
-    public AnonymousClassLoader() {
-        this.hostClass = checkHostClass(null);
-    }
-    public AnonymousClassLoader(Class<?> hostClass) {
-        this.hostClass = checkHostClass(hostClass);
+    // Privileged constructor.
+    private AnonymousClassLoader(Class<?> hostClass) {
+        this.hostClass = hostClass;
     }
 
-    private static Class<?> getTopLevelClass(Class<?> clazz) {
-      for(Class<?> outer = clazz.getDeclaringClass(); outer != null;
-          outer = outer.getDeclaringClass()) {
-        clazz = outer;
-      }
-      return clazz;
-    }
-
-    private static Class<?> checkHostClass(Class<?> hostClass) {
-        // called only from the constructor
-        // does a context-sensitive check on caller class
-        // CC[0..3] = {Reflection, this.checkHostClass, this.<init>, caller}
-        Class<?> caller = sun.reflect.Reflection.getCallerClass(CHC_CALLERS);
-
-        if (caller == null) {
-            // called from the JVM directly
-            if (hostClass == null)
-                return AnonymousClassLoader.class; // anything central will do
-            return hostClass;
-        }
-
-        if (hostClass == null)
-            hostClass = caller; // default value is caller itself
-
-        // anonymous class will access hostClass on behalf of caller
-        Class<?> callee = hostClass;
-
-        if (caller == callee)
-            // caller can always nominate itself to grant caller's own access rights
-            return hostClass;
-
-        // normalize caller and callee to their top-level classes:
-        caller = getTopLevelClass(caller);
-        callee = getTopLevelClass(callee);
-        if (caller == callee)
-            return caller;
-
-        ClassLoader callerCL = caller.getClassLoader();
-        if (callerCL == null) {
-            // caller is trusted code, so accept the proposed hostClass
-            return hostClass;
-        }
-
-        // %%% should do something with doPrivileged, because trusted
-        // code should have a way to execute on behalf of
-        // partially-trusted clients
-
-        // Does the caller have the right to access the private
-        // members of the callee?  If not, raise an error.
-        final int ACC_PRIVATE = 2;
-        try {
-            sun.reflect.Reflection.ensureMemberAccess(caller, callee, null, ACC_PRIVATE);
-        } catch (IllegalAccessException ee) {
-            throw new IllegalArgumentException(ee);
-        }
-
-        return hostClass;
+    public static AnonymousClassLoader make(sun.misc.Unsafe unsafe, Class<?> hostClass) {
+        if (unsafe == null)  throw new NullPointerException();
+        return new AnonymousClassLoader(hostClass);
     }
 
     public Class<?> loadClass(byte[] classFile) {
@@ -249,7 +189,7 @@
     private static int fakeNameCounter = 99999;
 
     // ignore two warnings on this line:
-    static sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
+    private static sun.misc.Unsafe unsafe = sun.misc.Unsafe.getUnsafe();
     // preceding line requires that this class be on the boot class path
 
     static private final Method defineAnonymousClass;