org.glassfish.pfl.basic.reflection.Bridge Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 1997-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package org.glassfish.pfl.basic.reflection;
import sun.reflect.ReflectionFactory;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.Objects;
/**
* This class provides the methods for fundamental JVM operations
* needed in the ORB that are not part of the public Java API. This includes:
*
* - throwException, which can throw undeclared checked exceptions.
* This is needed to handle throwing arbitrary exceptions across a standardized OMG interface that (incorrectly) does not specify appropriate exceptions.
* - putXXX/getXXX methods that allow unchecked access to fields of objects.
* This is used for setting uninitialzed non-static final fields (which is
* impossible with reflection) and for speed.
* - objectFieldOffset to obtain the field offsets for use in the putXXX/getXXX methods
* - newConstructorForSerialization to get the special constructor required for a
* Serializable class
* - latestUserDefinedLoader to get the latest user defined class loader from
* the call stack as required by the RMI-IIOP specification (really from the
* JDK 1.1 days)
*
* The code that calls Bridge.get() must have the following Permissions:
*
* - RuntimePermission "reflectionFactoryAccess"
* - BridgePermission "getBridge"
* - ReflectPermission "suppressAccessChecks"
*
*
* All of these permissions are required to obtain and correctly initialize
* the instance of Bridge. No security checks are performed on calls
* made to Bridge instance methods, so access to the Bridge instance
* must be protected.
*
* This class is a singleton (per ClassLoader of course). Access to the
* instance is obtained through the Bridge.get() method.
*/
public final class Bridge extends BridgeBase {
private static final Permission GET_BRIDGE_PERMISSION = new BridgePermission("getBridge");
private static Bridge bridge = null;
// latestUserDefinedLoader() is a private static method
// in ObjectInputStream in JDK 1.3 through 1.5.
// We use reflection in a doPrivileged block to get a
// Method reference and make it accessible.
private final Method latestUserDefinedLoaderMethod;
// Since java.io.OptionalDataException's constructors are
// package private, but we need to throw it in some special
// cases, we try to do it by reflection.
private final Constructor optionalDataExceptionConstructor;
private final ReflectionFactory reflectionFactory;
private Method getLatestUserDefinedLoaderMethod() {
return AccessController.doPrivileged(
new PrivilegedAction() {
@SuppressWarnings("unchecked")
public Method run() {
Method result;
try {
Class io = ObjectInputStream.class;
result = io.getDeclaredMethod("latestUserDefinedLoader");
result.setAccessible(true);
} catch (NoSuchMethodException nsme) {
throw new Error("java.io.ObjectInputStream latestUserDefinedLoader " + nsme, nsme);
}
return result;
}
}
);
}
// Grab the OptionalDataException boolean ctor and make it accessible.
@SuppressWarnings("unchecked")
private Constructor getOptDataExceptionCtor() {
try {
Constructor result = AccessController.doPrivileged(
new PrivilegedExceptionAction() {
public Constructor run()
throws NoSuchMethodException, SecurityException {
Constructor constructor = OptionalDataException.class.getDeclaredConstructor(Boolean.TYPE);
constructor.setAccessible(true);
return constructor;
}
}
);
if (result == null) {
throw new Error("Unable to find OptionalDataException constructor");
}
return (Constructor) result;
} catch (Exception ex) {
throw new Error("Unable to find OptionalDataException constructor");
}
}
@SuppressWarnings("unchecked")
private Bridge() {
latestUserDefinedLoaderMethod = getLatestUserDefinedLoaderMethod();
reflectionFactory = ReflectionFactory.getReflectionFactory();
optionalDataExceptionConstructor = getOptDataExceptionCtor();
}
/**
* Fetch the Bridge singleton. This requires the following
* permissions:
*
* - RuntimePermission "reflectionFactoryAccess"
* - BridgePermission "getBridge"
* - ReflectPermission "suppressAccessChecks"
*
*
* @return The singleton instance of the Bridge class
* @throws SecurityException if the caller does not have the
* required permissions and the caller has a non-null security manager.
*/
public static synchronized Bridge get() {
SecurityManager sman = System.getSecurityManager();
if (sman != null) {
sman.checkPermission(GET_BRIDGE_PERMISSION);
}
if (bridge == null) {
bridge = new Bridge();
}
return bridge;
}
@Override
public final ClassLoader getLatestUserDefinedLoader() {
try {
return (ClassLoader) latestUserDefinedLoaderMethod.invoke(null);
} catch (InvocationTargetException | IllegalAccessException ite) {
throw new Error(getClass().getName() + ".latestUserDefinedLoader: " + ite, ite);
}
}
@Override
@SuppressWarnings("unchecked")
public final Constructor newConstructorForExternalization(Class cl) {
try {
Constructor> cons = cl.getDeclaredConstructor();
cons.setAccessible(true);
return isPublic(cons) ? (Constructor) cons : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
private static boolean isPublic(Constructor> cons) {
return (cons.getModifiers() & Modifier.PUBLIC) != 0;
}
@Override
@SuppressWarnings("unchecked")
public final Constructor newConstructorForSerialization(Class aClass, Constructor> cons) {
Constructor newConstructor = reflectionFactory.newConstructorForSerialization(aClass, cons);
newConstructor.setAccessible(true);
return (Constructor) newConstructor;
}
@Override
public Constructor newConstructorForSerialization(Class aClass) {
Class> baseClass = getNearestNonSerializableBaseClass(aClass);
if (baseClass == null) return null;
try {
Constructor> cons = baseClass.getDeclaredConstructor();
if (isPrivate(cons) || !isAccessibleFromSubclass(cons, aClass, baseClass)) return null;
return newConstructorForSerialization(aClass, cons);
} catch (NoSuchMethodException ex) {
return null;
}
}
private static Class> getNearestNonSerializableBaseClass(Class clazz) {
Class> baseClass = clazz;
while (Serializable.class.isAssignableFrom(baseClass))
if ((baseClass = baseClass.getSuperclass()) == null) return null;
return baseClass;
}
private static boolean isAccessibleFromSubclass(Constructor> constructor, Class> clazz, Class> baseClass) {
return isPublicOrProtected(constructor) || inSamePackage(clazz, baseClass);
}
private static boolean inSamePackage(Class> clazz, Class> baseClass) {
return Objects.equals(clazz.getPackage(), baseClass.getPackage());
}
private static boolean isPublicOrProtected(Constructor> constructor) {
return (constructor.getModifiers() & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0;
}
private static boolean isPrivate(Constructor> cons) {
return (cons.getModifiers() & Modifier.PRIVATE) != 0;
}
private static Method hasStaticInitializerMethod = null;
@Override
public boolean hasStaticInitializerForSerialization(Class> cl) {
try {
return (Boolean) getHasStaticInitializerMethod().invoke(null, cl);
} catch (Exception ex) {
throw new Error("Cannot invoke 'hasStaticInitializer' method on " + ObjectStreamClass.class.getName());
}
}
private static Method getHasStaticInitializerMethod() throws NoSuchMethodException {
if (hasStaticInitializerMethod == null) {
hasStaticInitializerMethod = ObjectStreamClass.class.getDeclaredMethod("hasStaticInitializer", Class.class);
hasStaticInitializerMethod.setAccessible(true);
}
return hasStaticInitializerMethod;
}
@Override
public MethodHandle writeObjectForSerialization(Class> cl) {
return toMethodHandle(getPrivateMethod(cl, "writeObject", Void.TYPE, ObjectOutputStream.class));
}
private static MethodHandle toMethodHandle(Method method) {
try {
if (method == null) return null;
method.setAccessible(true);
MethodHandle methodHandle = MethodHandles.lookup().unreflect(method);
method.setAccessible(false);
return methodHandle;
} catch (SecurityException | IllegalAccessException e) {
return null;
}
}
private static Method getPrivateMethod(Class> cl, String name, Class> returnType, Class>... argTypes ) {
try {
Method method = cl.getDeclaredMethod(name, argTypes);
return ((method.getReturnType() == returnType) && isPrivate(method) && !isStatic(method)) ? method : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
private static boolean isStatic(Method method) {
return Modifier.isStatic(method.getModifiers());
}
private static boolean isPrivate(Method method) {
return Modifier.isPrivate(method.getModifiers());
}
@Override
public MethodHandle readObjectForSerialization(Class> cl) {
return toMethodHandle(getPrivateMethod(cl, "readObject", Void.TYPE, ObjectInputStream.class));
}
@Override
public MethodHandle readResolveForSerialization(Class> cl) {
return toMethodHandle(getInheritableMethod(cl, "readResolve", Object.class));
}
private static Method getInheritableMethod(Class> cl, String name, Class> returnType, Class>... argTypes ) {
Method method = getMatchingMethod(cl, name, returnType, argTypes);
return (method != null && isMethodInheritableBy(cl, method)) ? method : null;
}
private static Method getMatchingMethod(Class> cl, String name, Class> returnType, Class>[] argTypes) {
Class> aClass = cl;
while (aClass != null) {
try {
Method method = aClass.getDeclaredMethod(name, argTypes);
return method.getReturnType() == returnType ? method : null;
} catch (NoSuchMethodException ex) {
aClass = aClass.getSuperclass();
}
}
return null;
}
private static boolean isMethodInheritableBy(Class> callingClass, Method method) {
Class> baseClass = method.getDeclaringClass();
int mods = method.getModifiers();
if ((mods & (Modifier.STATIC | Modifier.ABSTRACT)) != 0) {
return false;
} else if ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) != 0) {
return true;
} else if ((mods & Modifier.PRIVATE) != 0) {
return (callingClass == baseClass);
} else {
return packageEquals(callingClass, baseClass);
}
}
/**
* Returns true if classes are defined in the same package, false
* Copied from the Merlin java.io.ObjectStreamClass.
*/
private static boolean packageEquals(Class> cl1, Class> cl2) {
Package pkg1 = cl1.getPackage(), pkg2 = cl2.getPackage();
return ((pkg1 == pkg2) || ((pkg1 != null) && (pkg1.equals(pkg2))));
}
@Override
public MethodHandle writeReplaceForSerialization(Class> cl) {
return toMethodHandle(getInheritableMethod(cl, "writeReplace", Object.class));
}
@Override
public OptionalDataException newOptionalDataExceptionForSerialization(boolean endOfData) {
try {
return optionalDataExceptionConstructor.newInstance(endOfData);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new Error("Unable to create OptionalDataException");
}
}
}