diff --git a/src/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/share/classes/java/lang/invoke/MethodHandleImpl.java index d9822bd7189a249c9a8090deee60eb9adc4af2b2..6013a417cc53fed4c6a109762635438ae1b900d4 100644 --- a/src/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -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,13 +25,14 @@ package java.lang.invoke; -import sun.invoke.util.VerifyType; - +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import sun.invoke.empty.Empty; import sun.invoke.util.ValueConversions; +import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.MethodHandleStatics.*; @@ -781,4 +782,168 @@ import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; return mh; } + /** + * 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); + } + C_Trampoline = tramp; + } + + 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); + } + return bccInvoker; + } + private static ClassValue CV_makeInjectedInvoker = new ClassValue() { + @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.internalForm().compileToBytecode(); // eliminate LFI stack frames + vamh = vamh.asSpreader(Object[].class, arity); + vamh.internalForm().compileToBytecode(); // eliminate LFI stack frames + 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); + } + } + + 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() { + public Void run() { + try { + Class 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); + } + 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); + } + } + } } diff --git a/src/share/classes/java/lang/invoke/MethodHandleNatives.java b/src/share/classes/java/lang/invoke/MethodHandleNatives.java index d27256fb3374491945ece6909b8d5789096b936c..06a9ba20e697e60d573f4deaef9f424fc1659a11 100644 --- a/src/share/classes/java/lang/invoke/MethodHandleNatives.java +++ b/src/share/classes/java/lang/invoke/MethodHandleNatives.java @@ -385,4 +385,101 @@ class MethodHandleNatives { throw 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? + */ + // 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; + } } diff --git a/src/share/classes/java/lang/invoke/MethodHandleStatics.java b/src/share/classes/java/lang/invoke/MethodHandleStatics.java index 3db5712fdeb6775a6c9dde0bf17d9dc9bae720e6..50e273804ec63142741f3032075ea5b0d8d540ae 100644 --- a/src/share/classes/java/lang/invoke/MethodHandleStatics.java +++ b/src/share/classes/java/lang/invoke/MethodHandleStatics.java @@ -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 @@ -108,7 +108,7 @@ import sun.misc.Unsafe; /*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) { throw new InternalError("uncaught exception", ex); } static Error NYI() { diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java index 5c55f20235394b9e6b3d5ad6dcae955e56322217..e7007dd2b8974f8a788e5806be4f068d219d3424 100644 --- a/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/share/classes/java/lang/invoke/MethodHandles.java @@ -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 @@ -329,6 +329,7 @@ public class MethodHandles { * where {@code defcPkg} is the package of {@code defc}. * */ + // FIXME in MR1: clarify that the bytecode behavior of a caller-ID method (like Class.forName) is relative to the lookupClass used to create the method handle, not the dynamic caller of the method handle public static final class Lookup { /** The class on behalf of whom the lookup is being performed. */ @@ -1209,6 +1210,7 @@ return mh1; if (method.isMethodHandleInvoke()) return fakeMethodHandleInvoke(method); MethodHandle mh = DirectMethodHandle.make(refc, method); + mh = maybeBindCaller(method, mh); mh = mh.setVarargs(method); if (doRestrict) mh = restrictReceiver(method, mh, lookupClass()); @@ -1217,6 +1219,16 @@ return mh1; private MethodHandle fakeMethodHandleInvoke(MemberName method) { return throwException(method.getReturnType(), UnsupportedOperationException.class); } + 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); + // Note: caller will apply varargs after this step happens. + return cbmh; + } private MethodHandle getDirectField(byte refKind, Class refc, MemberName field) throws IllegalAccessException { checkField(refKind, refc, field); MethodHandle mh = DirectMethodHandle.make(refc, field); @@ -1229,6 +1241,7 @@ return mh1; private MethodHandle getDirectConstructor(Class refc, MemberName ctor) throws IllegalAccessException { assert(ctor.isConstructor()); checkAccess(REF_newInvokeSpecial, refc, ctor); + assert(!MethodHandleNatives.isCallerSensitive(ctor)); // maybeBindCaller not relevant here return DirectMethodHandle.make(ctor).setVarargs(ctor); } diff --git a/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java b/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java index 3005a452f71e39716b7727660e09e09b273941c1..32624b40f97760719159031009f43824d5d5790d 100644 --- a/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java +++ b/src/share/classes/sun/invoke/anon/AnonymousClassLoader.java @@ -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 @@ import sun.misc.IOUtils; 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., 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 @@ public class AnonymousClassLoader { 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; diff --git a/src/share/classes/sun/invoke/util/ValueConversions.java b/src/share/classes/sun/invoke/util/ValueConversions.java index 1533f4b2ad211671ee7c90896ccc2c0012e710f0..b2044818f60399424e02bc386d7ad49540edee1e 100644 --- a/src/share/classes/sun/invoke/util/ValueConversions.java +++ b/src/share/classes/sun/invoke/util/ValueConversions.java @@ -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 @@ -522,13 +522,19 @@ public class ValueConversions { static { MethodHandle mh = null; try { - java.lang.reflect.Method m = MethodHandles.class + final java.lang.reflect.Method m = MethodHandles.class .getDeclaredMethod("collectArguments", MethodHandle.class, int.class, MethodHandle.class); - m.setAccessible(true); + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + m.setAccessible(true); + return null; + } + }); mh = IMPL_LOOKUP.unreflect(m); - } catch (ReflectiveOperationException | SecurityException ex) { + } catch (ReflectiveOperationException ex) { throw new InternalError(ex); } COLLECT_ARGUMENTS = mh; diff --git a/test/java/lang/invoke/7196190/ClassForNameTest.java b/test/java/lang/invoke/7196190/ClassForNameTest.java new file mode 100644 index 0000000000000000000000000000000000000000..48ccb2a6463425944465ea4a566741e0c93eb4e6 --- /dev/null +++ b/test/java/lang/invoke/7196190/ClassForNameTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 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 + * 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 7196190 + * @summary Improve method of handling MethodHandles + * + * @run main/othervm ClassForNameTest + */ + +import java.lang.invoke.*; +import java.lang.reflect.Method; +import java.util.Arrays; + +public class ClassForNameTest { + final static String NAME = ClassForNameTest.class.getName(); + + public static void main(String[] args) throws Throwable { + { + final MethodType mt = MethodType.methodType(Class.class, String.class); + final MethodHandle mh = MethodHandles.lookup() + .findStatic(Class.class, "forName", mt); + + Class.forName(NAME); + + mh.invoke(NAME); + mh.bindTo(NAME).invoke(); + mh.invokeWithArguments(Arrays.asList(NAME)); + mh.invokeWithArguments(NAME); + Class cls = (Class) mh.invokeExact(NAME); + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodType mt = MethodType.methodType(Object.class, Object.class, Object[].class); + final MethodHandle mh = MethodHandles.lookup() + .findVirtual(Method.class, "invoke", mt) + .bindTo(fnMethod); + + fnMethod.invoke(null, NAME); + + mh.bindTo(null).bindTo(new Object[]{NAME}).invoke(); + mh.invoke(null, new Object[]{NAME}); + mh.invokeWithArguments(null, new Object[]{NAME}); + mh.invokeWithArguments(Arrays.asList(null, new Object[]{NAME})); + Object obj = mh.invokeExact((Object) null, new Object[]{NAME}); + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodType mt = MethodType.methodType(Object.class, Object.class, Object[].class); + + final MethodHandle mh = MethodHandles.lookup() + .bind(fnMethod, "invoke", mt); + + mh.bindTo(null).bindTo(new Object[]{NAME}).invoke(); + mh.invoke(null, new Object[]{NAME}); + mh.invokeWithArguments(null, NAME); + mh.invokeWithArguments(Arrays.asList(null, NAME)); + Object obj = mh.invokeExact((Object) null, new Object[]{NAME}); + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodHandle mh = MethodHandles.lookup().unreflect(fnMethod); + + mh.bindTo(NAME).invoke(); + mh.invoke(NAME); + mh.invokeWithArguments(NAME); + mh.invokeWithArguments(Arrays.asList(NAME)); + Class cls = (Class) mh.invokeExact(NAME); + } + + System.out.println("TEST PASSED"); + } +} diff --git a/test/java/lang/invoke/7196190/GetUnsafeTest.java b/test/java/lang/invoke/7196190/GetUnsafeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..0fd3cbd8d0f938cd730a34bd2284a3ecbd6437ea --- /dev/null +++ b/test/java/lang/invoke/7196190/GetUnsafeTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 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 + * 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 7196190 + * @summary Improve method of handling MethodHandles + * + * @run main/othervm/policy=jtreg.security.policy/secure=java.lang.SecurityManager GetUnsafeTest + */ + +import java.lang.invoke.*; +import java.lang.reflect.Method; +import java.util.Arrays; + +public class GetUnsafeTest { + final static String NAME = "sun.misc.Unsafe"; + + private static boolean isTestFailed = false; + + private static void fail() { + isTestFailed = true; + try { throw new Exception(); } catch (Throwable e) { + StackTraceElement frame = e.getStackTrace()[1]; + System.out.printf("Failed at %s:%d\n", frame.getFileName(), frame.getLineNumber()); + } + } + + public static void main(String[] args) throws Throwable { + { + final MethodType mt = MethodType.methodType(Class.class, String.class); + final MethodHandle mh = MethodHandles.lookup() + .findStatic(Class.class, "forName", mt); + + try { Class.forName(NAME); fail(); } catch (Throwable e) {} + + try { mh.invoke(NAME); fail(); } catch (Throwable e) {} + try { mh.bindTo(NAME).invoke(); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(Arrays.asList(NAME)); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(NAME); fail(); } catch (Throwable e) {} + try { Class cls = (Class) mh.invokeExact(NAME); fail(); } catch (Throwable e) {} + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodType mt = MethodType.methodType(Object.class, Object.class, Object[].class); + final MethodHandle mh = MethodHandles.lookup() + .findVirtual(Method.class, "invoke", mt) + .bindTo(fnMethod); + + try { fnMethod.invoke(null, NAME); fail(); } catch (Throwable e) {} + + try { mh.bindTo(null).bindTo(new Object[]{NAME}).invoke(); fail(); } catch (Throwable e) {} + try { mh.invoke(null, new Object[]{NAME}); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(null, new Object[]{NAME}); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(Arrays.asList(null, new Object[]{NAME})); fail(); } catch (Throwable e) {} + try { Object obj = mh.invokeExact((Object) null, new Object[]{NAME}); fail(); } catch (Throwable e) {} + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodType mt = MethodType.methodType(Object.class, Object.class, Object[].class); + + final MethodHandle mh = MethodHandles.lookup().bind(fnMethod, "invoke", mt); + + try { mh.bindTo(null).bindTo(new Object[]{NAME}).invoke(); fail(); } catch (Throwable e) {} + try { mh.invoke(null, new Object[]{NAME}); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(null, NAME); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(Arrays.asList(null, NAME)); fail(); } catch (Throwable e) {} + try { Object obj = mh.invokeExact((Object) null, new Object[]{NAME}); fail(); } catch (Throwable e) {} + } + + { + final Method fnMethod = Class.class.getMethod("forName", String.class); + final MethodHandle mh = MethodHandles.lookup().unreflect(fnMethod); + + try { mh.bindTo(NAME).invoke(); fail(); } catch (Throwable e) {} + try { mh.invoke(NAME); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(NAME); fail(); } catch (Throwable e) {} + try { mh.invokeWithArguments(Arrays.asList(NAME)); fail(); } catch (Throwable e) {} + try { Class cls = (Class) mh.invokeExact(NAME); fail(); } catch (Throwable e) {} + } + + if (!isTestFailed) { + System.out.println("TEST PASSED"); + } else { + System.out.println("TEST FAILED"); + System.exit(1); + } + } +} diff --git a/test/java/lang/invoke/7196190/MHProxyTest.java b/test/java/lang/invoke/7196190/MHProxyTest.java new file mode 100644 index 0000000000000000000000000000000000000000..5e07f393cb1214c097612a9a33bbcd9e4f95c11e --- /dev/null +++ b/test/java/lang/invoke/7196190/MHProxyTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 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 + * 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 7196190 + * @summary Improve method of handling MethodHandles + * + * @run main/othervm MHProxyTest + */ + +import java.lang.invoke.*; +import java.security.*; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; + +public class MHProxyTest { + private static final Class C_Unsafe; + private static final MethodHandle MH_getUnsafe; + static { + // Do these before there is a SM installed. + C_Unsafe = sun.misc.Unsafe.class; // EXPECT A WARNING ON THIS LINE + Lookup lookup = lookup(); + MethodHandle gumh = null; + try { + gumh = lookup.findStatic(C_Unsafe, "getUnsafe", methodType(C_Unsafe)); + } catch (ReflectiveOperationException ex) { + throw new InternalError(ex.toString()); + } + MH_getUnsafe = gumh; + // Try some different lookups: + try { + lookup.in(Object.class).findStatic(C_Unsafe, "getUnsafe", methodType(C_Unsafe)); + } catch (ReflectiveOperationException ex) { + throw new InternalError(ex.toString()); + } + lookup = lookup().in(C_Unsafe); + try { + lookup.in(C_Unsafe).findStatic(C_Unsafe, "getUnsafe", methodType(C_Unsafe)); + } catch (ReflectiveOperationException ex) { + throw new InternalError(ex.toString()); + } + } + + public static void main(String[] args) throws Throwable { + System.setSecurityManager(new SecurityManager()); + Lookup lookup = lookup(); + testBasic(lookup); + testDoPriv(lookup); + testSetVar(); + Lookup l2 = lookup.in(Object.class); + System.out.println("=== "+l2); + testBasic(l2); + testDoPriv(l2); + Lookup l3 = lookup.in(C_Unsafe); + System.out.println("=== "+l3); + testBasic(l3); + testDoPriv(l3); + if (failure != null) + throw failure; + } + + private static Throwable failure; + private static void fail(Throwable ex) { + if (failure == null) + failure = ex; + StackTraceElement frame = new Exception().getStackTrace()[1]; + System.out.printf("Failed at %s:%d: %s\n", frame.getFileName(), frame.getLineNumber(), ex); + } + private static void ok(Throwable ex) { + StackTraceElement frame = new Exception().getStackTrace()[1]; + System.out.printf("OK at %s:%d: %s\n", frame.getFileName(), frame.getLineNumber(), ex); + } + + private static void testBasic(Lookup lookup) throws Throwable { + // Verify that we can't get to this guy under the SM: + try { + MethodHandle badmh = lookup.findStatic(C_Unsafe, "getUnsafe", methodType(C_Unsafe)); + assert(badmh.type() == methodType(C_Unsafe)); + badmh = badmh.asType(badmh.type().generic()); + Object u = C_Unsafe.cast(badmh.invokeExact()); + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got mh to getUnsafe!")); + } catch (SecurityException ex) { + ok(ex); + } + try { + Object u = MH_getUnsafe.invokeWithArguments(); + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got the Unsafe object! (MH invoke)")); + } catch (SecurityException ex) { + ok(ex); + } + try { + MethodHandle mh = MH_getUnsafe; + mh = mh.asType(mh.type().generic()); + mh = foldArguments(identity(Object.class), mh); + mh = filterReturnValue(mh, identity(Object.class)); + Object u = mh.invokeExact(); + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got the Unsafe object! (MH invokeWithArguments)")); + } catch (SecurityException ex) { + ok(ex); + } + } + + private static void testDoPriv(Lookup lookup) throws Throwable { + PrivilegedAction privAct = MethodHandleProxies.asInterfaceInstance(PrivilegedAction.class, MH_getUnsafe); + try { + Object u = AccessController.doPrivileged(privAct); + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got the Unsafe object! (static doPriv)")); + } catch (SecurityException ex) { + ok(ex); + } + MethodHandle MH_doPriv = lookup.findStatic(AccessController.class, "doPrivileged", + methodType(Object.class, PrivilegedAction.class)); + MH_doPriv = MH_doPriv.bindTo(privAct); + try { + Object u = MH_doPriv.invoke(); + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got the Unsafe object! (MH + doPriv)")); + } catch (SecurityException ex) { + ok(ex); + } + // try one more layer of indirection: + Runnable rbl = MethodHandleProxies.asInterfaceInstance(Runnable.class, MH_doPriv); + try { + rbl.run(); + fail(new AssertionError("got the Unsafe object! (Runnable + MH + doPriv)")); + } catch (SecurityException ex) { + ok(ex); + } + } + + private static void testSetVar() throws Throwable { + { + // Test the box pattern: + Object[] box = new Object[1]; + MethodHandle MH_getFoo = identity(Object.class).bindTo("foo"); + MethodHandle MH_storeToBox = insertArguments(arrayElementSetter(Object[].class), 0, box, 0); + MethodHandle mh = filterReturnValue(MH_getFoo, MH_storeToBox); + mh.invokeExact(); + assert(box[0] == "foo"); + } + { + Object[] box = new Object[1]; + MethodHandle MH_storeToBox = insertArguments(arrayElementSetter(Object[].class), 0, box, 0); + MethodHandle mh = filterReturnValue(MH_getUnsafe.asType(MH_getUnsafe.type().generic()), MH_storeToBox); + try { + mh.invokeExact(); + Object u = box[0]; + assert(C_Unsafe.isInstance(u)); + fail(new AssertionError("got the Unsafe object! (MH + setElement)")); + } catch (SecurityException ex) { + ok(ex); + } + } + } +} diff --git a/test/java/lang/invoke/7196190/jtreg.security.policy b/test/java/lang/invoke/7196190/jtreg.security.policy new file mode 100644 index 0000000000000000000000000000000000000000..d32c7af9b3f7d951d829919ddd8328d1ceff01a6 --- /dev/null +++ b/test/java/lang/invoke/7196190/jtreg.security.policy @@ -0,0 +1,9 @@ +/* + * security policy used by the test process + * must allow file reads so that jtreg itself can run + */ + +grant { + // standard test activation permissions + permission java.io.FilePermission "*", "read"; +};