提交 bbcc62de 编写于 作者: R rriggs

8164908: ReflectionFactory support for IIOP and custom serialization

Reviewed-by: chegar, psandoz
上级 7279024e
......@@ -680,7 +680,9 @@ public class MethodHandles {
// disallow lookup more restricted packages
if (allowedModes == ALL_MODES && lookupClass.getClassLoader() == null) {
if (name.startsWith("java.") ||
(name.startsWith("sun.") && !name.startsWith("sun.invoke."))) {
(name.startsWith("sun.")
&& !name.startsWith("sun.invoke.")
&& !name.equals("sun.reflect.ReflectionFactory"))) {
throw newIllegalArgumentException("illegal lookupClass: " + lookupClass);
}
}
......
......@@ -25,16 +25,28 @@
package sun.reflect;
import java.io.Externalizable;
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.Field;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.util.Objects;
import sun.reflect.misc.ReflectUtil;
/** <P> The master factory for all reflective objects, both those in
java.lang.reflect (Fields, Methods, Constructors) as well as their
delegates (FieldAccessors, MethodAccessors, ConstructorAccessors).
......@@ -56,6 +68,9 @@ public class ReflectionFactory {
// Provides access to package-private mechanisms in java.lang.reflect
private static volatile LangReflectAccess langReflectAccess;
/* Method for static class initializer <clinit>, or null */
private static volatile Method hasStaticInitializerMethod;
//
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
......@@ -73,8 +88,7 @@ public class ReflectionFactory {
private static boolean noInflation = false;
private static int inflationThreshold = 15;
private ReflectionFactory() {
}
private ReflectionFactory() {}
/**
* A convenience class for acquiring the capability to instantiate
......@@ -328,6 +342,14 @@ public class ReflectionFactory {
//
//
/**
* Returns an accessible constructor capable of creating instances
* of the given class, initialized by the given constructor.
*
* @param classToInstantiate the class to instantiate
* @param constructorToCall the constructor to call
* @return an accessible constructor
*/
public Constructor<?> newConstructorForSerialization
(Class<?> classToInstantiate, Constructor<?> constructorToCall)
{
......@@ -335,6 +357,42 @@ public class ReflectionFactory {
if (constructorToCall.getDeclaringClass() == classToInstantiate) {
return constructorToCall;
}
return generateConstructor(classToInstantiate, constructorToCall);
}
/**
* Returns an accessible no-arg constructor for a class.
* The no-arg constructor is found searching the class and its supertypes.
*
* @param cl the class to instantiate
* @return a no-arg constructor for the class or {@code null} if
* the class or supertypes do not have a suitable no-arg constructor
*/
public final Constructor<?> newConstructorForSerialization(Class<?> cl) {
Class<?> initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
if ((initCl = initCl.getSuperclass()) == null) {
return null;
}
}
Constructor<?> constructorToCall;
try {
constructorToCall = initCl.getDeclaredConstructor();
int mods = constructorToCall.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl))) {
return null;
}
} catch (NoSuchMethodException ex) {
return null;
}
return generateConstructor(cl, constructorToCall);
}
private final Constructor<?> generateConstructor(Class<?> classToInstantiate,
Constructor<?> constructorToCall) {
ConstructorAccessor acc = new MethodAccessorGenerator().
generateSerializationConstructor(classToInstantiate,
......@@ -355,9 +413,222 @@ public class ReflectionFactory {
langReflectAccess().
getConstructorParameterAnnotations(constructorToCall));
setConstructorAccessor(c, acc);
c.setAccessible(true);
return c;
}
/**
* Returns an accessible no-arg constructor for an externalizable class to be
* initialized using a public no-argument constructor.
*
* @param cl the class to instantiate
* @return A no-arg constructor for the class; returns {@code null} if
* the class does not implement {@link java.io.Externalizable}
*/
public final Constructor<?> newConstructorForExternalization(Class<?> cl) {
if (!Externalizable.class.isAssignableFrom(cl)) {
return null;
}
try {
Constructor<?> cons = cl.getConstructor();
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;
}
}
/**
* Returns a direct MethodHandle for the {@code readObject} method on
* a Serializable class.
* The first argument of {@link MethodHandle#invoke} is the serializable
* object and the second argument is the {@code ObjectInputStream} passed to
* {@code readObject}.
*
* @param cl a Serializable class
* @return a direct MethodHandle for the {@code readObject} method of the class or
* {@code null} if the class does not have a {@code readObject} method
*/
public final MethodHandle readObjectForSerialization(Class<?> cl) {
return findReadWriteObjectForSerialization(cl, "readObject", ObjectInputStream.class);
}
/**
* Returns a direct MethodHandle for the {@code readObjectNoData} method on
* a Serializable class.
* The first argument of {@link MethodHandle#invoke} is the serializable
* object and the second argument is the {@code ObjectInputStream} passed to
* {@code readObjectNoData}.
*
* @param cl a Serializable class
* @return a direct MethodHandle for the {@code readObjectNoData} method
* of the class or {@code null} if the class does not have a
* {@code readObjectNoData} method
*/
public final MethodHandle readObjectNoDataForSerialization(Class<?> cl) {
return findReadWriteObjectForSerialization(cl, "readObjectNoData", ObjectInputStream.class);
}
/**
* Returns a direct MethodHandle for the {@code writeObject} method on
* a Serializable class.
* The first argument of {@link MethodHandle#invoke} is the serializable
* object and the second argument is the {@code ObjectOutputStream} passed to
* {@code writeObject}.
*
* @param cl a Serializable class
* @return a direct MethodHandle for the {@code writeObject} method of the class or
* {@code null} if the class does not have a {@code writeObject} method
*/
public final MethodHandle writeObjectForSerialization(Class<?> cl) {
return findReadWriteObjectForSerialization(cl, "writeObject", ObjectOutputStream.class);
}
private final MethodHandle findReadWriteObjectForSerialization(Class<?> cl,
String methodName,
Class<?> streamClass) {
if (!Serializable.class.isAssignableFrom(cl)) {
return null;
}
try {
Method meth = cl.getDeclaredMethod(methodName, streamClass);
int mods = meth.getModifiers();
if (meth.getReturnType() != Void.TYPE ||
Modifier.isStatic(mods) ||
!Modifier.isPrivate(mods)) {
return null;
}
meth.setAccessible(true);
return MethodHandles.lookup().unreflect(meth);
} catch (NoSuchMethodException ex) {
return null;
} catch (IllegalAccessException ex1) {
throw new InternalError("Error", ex1);
}
}
/**
* Returns a direct MethodHandle for the {@code readResolve} method on
* a serializable class.
* The single argument of {@link MethodHandle#invoke} is the serializable
* object.
*
* @param cl the Serializable class
* @return a direct MethodHandle for the {@code readResolve} method of the class or
* {@code null} if the class does not have a {@code readResolve} method
*/
public final MethodHandle readResolveForSerialization(Class<?> cl) {
return getReplaceResolveForSerialization(cl, "readResolve");
}
/**
* Returns a direct MethodHandle for the {@code writeReplace} method on
* a serializable class.
* The single argument of {@link MethodHandle#invoke} is the serializable
* object.
*
* @param cl the Serializable class
* @return a direct MethodHandle for the {@code writeReplace} method of the class or
* {@code null} if the class does not have a {@code writeReplace} method
*/
public final MethodHandle writeReplaceForSerialization(Class<?> cl) {
return getReplaceResolveForSerialization(cl, "writeReplace");
}
/**
* Returns a direct MethodHandle for the {@code writeReplace} method on
* a serializable class.
* The single argument of {@link MethodHandle#invoke} is the serializable
* object.
*
* @param cl the Serializable class
* @return a direct MethodHandle for the {@code writeReplace} method of the class or
* {@code null} if the class does not have a {@code writeReplace} method
*/
private MethodHandle getReplaceResolveForSerialization(Class<?> cl,
String methodName) {
if (!Serializable.class.isAssignableFrom(cl)) {
return null;
}
Class<?> defCl = cl;
while (defCl != null) {
try {
Method m = defCl.getDeclaredMethod(methodName);
if (m.getReturnType() != Object.class) {
return null;
}
int mods = m.getModifiers();
if (Modifier.isStatic(mods) | Modifier.isAbstract(mods)) {
return null;
} else if (Modifier.isPublic(mods) | Modifier.isProtected(mods)) {
// fall through
} else if (Modifier.isPrivate(mods) && (cl != defCl)) {
return null;
} else if (!packageEquals(cl, defCl)) {
return null;
}
try {
// Normal return
m.setAccessible(true);
return MethodHandles.lookup().unreflect(m);
} catch (IllegalAccessException ex0) {
// setAccessible should prevent IAE
throw new InternalError("Error", ex0);
}
} catch (NoSuchMethodException ex) {
defCl = defCl.getSuperclass();
}
}
return null;
}
/**
* Returns true if the class has a static initializer.
* The presence of a static initializer is used to compute the serialVersionUID.
* @param cl a serializable classLook
* @return {@code true} if the class has a static initializer,
* otherwise {@code false}
*/
public final boolean hasStaticInitializerForSerialization(Class<?> cl) {
Method m = hasStaticInitializerMethod;
if (m == null) {
try {
m = ObjectStreamClass.class.getDeclaredMethod("hasStaticInitializer",
new Class<?>[]{Class.class});
m.setAccessible(true);
hasStaticInitializerMethod = m;
} catch (NoSuchMethodException ex) {
throw new InternalError("No such method hasStaticInitializer on "
+ ObjectStreamClass.class, ex);
}
}
try {
return (Boolean) m.invoke(null, cl);
} catch (InvocationTargetException | IllegalAccessException ex) {
throw new InternalError("Exception invoking hasStaticInitializer", ex);
}
}
/**
* Returns a new OptionalDataException with {@code eof} set to {@code true}
* or {@code false}.
* @param bool the value of {@code eof} in the created OptionalDataException
* @return a new OptionalDataException
*/
public final OptionalDataException newOptionalDataExceptionForSerialization(boolean bool) {
try {
Constructor<OptionalDataException> boolCtor =
OptionalDataException.class.getDeclaredConstructor(Boolean.TYPE);
boolCtor.setAccessible(true);
return boolCtor.newInstance(bool);
} catch (NoSuchMethodException | InstantiationException|
IllegalAccessException|InvocationTargetException ex) {
throw new InternalError("unable to create OptionalDataException", ex);
}
}
//--------------------------------------------------------------------------
//
// Internals only below this point
......@@ -421,4 +692,17 @@ public class ReflectionFactory {
}
return langReflectAccess;
}
/**
* Returns true if classes are defined in the classloader and same package, false
* otherwise.
* @param cl1 a class
* @param cl2 another class
* @returns true if the two classes are in the same classloader and package
*/
private static boolean packageEquals(Class<?> cl1, Class<?> cl2) {
return cl1.getClassLoader() == cl2.getClassLoader() &&
Objects.equals(cl1.getPackage(), cl2.getPackage());
}
}
/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* 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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import sun.reflect.ReflectionFactory;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.annotations.DataProvider;
import org.testng.TestNG;
/*
* @test
* @bug 8137058 8164908 8168980
* @run testng ReflectionFactoryTest
* @run testng/othervm/policy=security.policy ReflectionFactoryTest
* @summary Basic test for the unsupported ReflectionFactory
*/
public class ReflectionFactoryTest {
// Initialized by init()
static ReflectionFactory factory;
@DataProvider(name = "ClassConstructors")
static Object[][] classConstructors() {
return new Object[][] {
{Object.class},
{Foo.class},
{Bar.class},
};
}
@BeforeClass
static void init() {
factory = ReflectionFactory.getReflectionFactory();
}
/**
* Test that the correct Constructor is selected and run.
* @param type type of object to create
* @throws NoSuchMethodException - error
* @throws InstantiationException - error
* @throws IllegalAccessException - error
* @throws InvocationTargetException - error
*/
@Test(dataProvider="ClassConstructors")
static void testConstructor(Class<?> type)
throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException
{
@SuppressWarnings("unchecked")
Constructor<?> c = factory.newConstructorForSerialization(type);
Object o = c.newInstance();
Assert.assertEquals(o.getClass(), type, "Instance is wrong type");
if (o instanceof Foo) {
Foo foo = (Foo)o;
foo.check();
}
}
@DataProvider(name = "NonSerialConstructors")
static Object[][] constructors() throws NoSuchMethodException {
return new Object[][] {
{Foo.class, Object.class.getDeclaredConstructor()},
{Foo.class, Foo.class.getDeclaredConstructor()},
{Baz.class, Object.class.getDeclaredConstructor()},
{Baz.class, Foo.class.getDeclaredConstructor()},
{Baz.class, Baz.class.getDeclaredConstructor()}
};
}
/**
* Tests that the given Constructor, in the hierarchy, is run.
*/
@Test(dataProvider="NonSerialConstructors")
static void testNonSerializableConstructor(Class<?> cl,
Constructor<?> constructorToCall)
throws ReflectiveOperationException
{
@SuppressWarnings("unchecked")
Constructor<?> c = factory.newConstructorForSerialization(cl,
constructorToCall);
Object o = c.newInstance();
Assert.assertEquals(o.getClass(), cl, "Instance is wrong type");
int expectedFoo = 0;
int expectedBaz = 0;
if (constructorToCall.getName().equals("ReflectionFactoryTest$Foo")) {
expectedFoo = 1;
} else if (constructorToCall.getName().equals("ReflectionFactoryTest$Baz")) {
expectedFoo = 1;
expectedBaz = 4;
}
Assert.assertEquals(((Foo)o).foo(), expectedFoo);
if (o instanceof Baz) {
Assert.assertEquals(((Baz)o).baz(), expectedBaz);
}
}
static class Foo {
private int foo;
public Foo() {
this.foo = 1;
}
public String toString() {
return "foo: " + foo;
}
public void check() {
int expectedFoo = 1;
Assert.assertEquals(foo, expectedFoo, "foo() constructor not run");
}
public int foo() { return foo; }
}
static class Bar extends Foo implements Serializable {
private static final long serialVersionUID = 3L;
private int bar;
public Bar() {
this.bar = 1;
}
public String toString() {
return super.toString() + ", bar: " + bar;
}
public void check() {
super.check();
int expectedBar = 0;
Assert.assertEquals(bar, expectedBar, "bar() constructor not run");
}
}
static class Baz extends Foo {
private static final long serialVersionUID = 4L;
private final int baz;
public Baz() { this.baz = 4; }
public int baz() { return baz; }
}
/**
* Test newConstructorForExternalization returns the constructor and it can be called.
* @throws NoSuchMethodException - error
* @throws InstantiationException - error
* @throws IllegalAccessException - error
* @throws InvocationTargetException - error
*/
@Test
static void newConstructorForExternalization()
throws NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException {
Constructor<?> cons = factory.newConstructorForExternalization(Ext.class);
Ext ext = (Ext)cons.newInstance();
Assert.assertEquals(ext.ext, 1, "Constructor not run");
}
static class Ext implements Externalizable {
private static final long serialVersionUID = 1L;
int ext;
public Ext() {
ext = 1;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {}
}
@Test
static void testReadWriteObjectForSerialization() throws Throwable {
MethodHandle readObjectMethod = factory.readObjectForSerialization(Ser.class);
Assert.assertNotNull(readObjectMethod, "readObjectMethod not found");
MethodHandle readObjectNoDataMethod = factory.readObjectNoDataForSerialization(Ser.class);
Assert.assertNotNull(readObjectNoDataMethod, "readObjectNoDataMethod not found");
MethodHandle writeObjectMethod = factory.writeObjectForSerialization(Ser.class);
Assert.assertNotNull(writeObjectMethod, "writeObjectMethod not found");
MethodHandle readResolveMethod = factory.readResolveForSerialization(Ser.class);
Assert.assertNotNull(readResolveMethod, "readResolveMethod not found");
MethodHandle writeReplaceMethod = factory.writeReplaceForSerialization(Ser.class);
Assert.assertNotNull(writeReplaceMethod, "writeReplaceMethod not found");
byte[] data = null;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos)) {
Ser ser = new Ser();
writeReplaceMethod.invoke(ser);
Assert.assertTrue(ser.writeReplaceCalled, "writeReplace not called");
Assert.assertFalse(ser.writeObjectCalled, "writeObject should not have been called");
writeObjectMethod.invoke(ser, oos);
Assert.assertTrue(ser.writeReplaceCalled, "writeReplace should have been called");
Assert.assertTrue(ser.writeObjectCalled, "writeObject not called");
oos.flush();
data = baos.toByteArray();
}
try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais)) {
Ser ser2 = new Ser();
readObjectMethod.invoke(ser2, ois);
Assert.assertTrue(ser2.readObjectCalled, "readObject not called");
Assert.assertFalse(ser2.readObjectNoDataCalled, "readObjectNoData should not be called");
Assert.assertFalse(ser2.readResolveCalled, "readResolve should not be called");
readObjectNoDataMethod.invoke(ser2, ois);
Assert.assertTrue(ser2.readObjectCalled, "readObject should have been called");
Assert.assertTrue(ser2.readObjectNoDataCalled, "readObjectNoData not called");
Assert.assertFalse(ser2.readResolveCalled, "readResolve should not be called");
readResolveMethod.invoke(ser2);
Assert.assertTrue(ser2.readObjectCalled, "readObject should have been called");
Assert.assertTrue(ser2.readObjectNoDataCalled, "readObjectNoData not called");
Assert.assertTrue(ser2.readResolveCalled, "readResolve not called");
}
}
@Test
static void hasStaticInitializer() {
boolean actual = factory.hasStaticInitializerForSerialization(Ser.class);
Assert.assertTrue(actual, "hasStaticInitializerForSerialization is wrong");
}
static class Ser implements Serializable {
private static final long serialVersionUID = 2L;
static {
// Define a static class initialization method
}
boolean readObjectCalled = false;
boolean readObjectNoDataCalled = false;
boolean writeObjectCalled = false;
boolean readResolveCalled = false;
boolean writeReplaceCalled = false;
public Ser() {}
private void readObject(ObjectInputStream ois) throws IOException {
Assert.assertFalse(writeObjectCalled, "readObject called too many times");
readObjectCalled = ois.readBoolean();
}
private void readObjectNoData(ObjectInputStream ois) throws IOException {
Assert.assertFalse(readObjectNoDataCalled, "readObjectNoData called too many times");
readObjectNoDataCalled = true;
}
private void writeObject(ObjectOutputStream oos) throws IOException {
Assert.assertFalse(writeObjectCalled, "writeObject called too many times");
writeObjectCalled = true;
oos.writeBoolean(writeObjectCalled);
}
private Object writeReplace() {
Assert.assertFalse(writeReplaceCalled, "writeReplace called too many times");
writeReplaceCalled = true;
return this;
}
private Object readResolve() {
Assert.assertFalse(readResolveCalled, "readResolve called too many times");
readResolveCalled = true;
return this;
}
}
/**
* Test the constructor of OptionalDataExceptions.
*/
@Test
static void newOptionalDataException() {
OptionalDataException ode = factory.newOptionalDataExceptionForSerialization(true);
Assert.assertTrue(ode.eof, "eof wrong");
ode = factory.newOptionalDataExceptionForSerialization(false);
Assert.assertFalse(ode.eof, "eof wrong");
}
// Main can be used to run the tests from the command line with only testng.jar.
@SuppressWarnings("raw_types")
@Test(enabled = false)
public static void main(String[] args) {
Class<?>[] testclass = {ReflectionFactoryTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
}
// Individual Permissions for ReflectionFactoryTest
grant {
// Permissions needed to run the test
permission java.util.PropertyPermission "*", "read";
permission java.io.FilePermission "<<ALL FILES>>", "read,write,delete,execute";
permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
permission java.lang.RuntimePermission "accessDeclaredMembers";
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
permission java.lang.RuntimePermission "reflectionFactoryAccess";
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册