diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java index acac29be9abc53bb8fc7542932378e3bebaf5b72..7b9353ab0f1b8fc8764c83384ea644afaed66c85 100644 --- a/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/share/classes/java/lang/invoke/MethodHandles.java @@ -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); } } diff --git a/src/share/classes/sun/reflect/ReflectionFactory.java b/src/share/classes/sun/reflect/ReflectionFactory.java index dc26fab816b51ad419050964f068b63f6af57720..95408bd931bb04a79d7bb7c712e5233d7aa8b1ed 100644 --- a/src/share/classes/sun/reflect/ReflectionFactory.java +++ b/src/share/classes/sun/reflect/ReflectionFactory.java @@ -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; + /**

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 , 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 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()); + } + } diff --git a/test/sun/reflect/ReflectionFactory/ReflectionFactoryTest.java b/test/sun/reflect/ReflectionFactory/ReflectionFactoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..68e0d49954c7fad7443d5a6fabb6bbd5ca3011b2 --- /dev/null +++ b/test/sun/reflect/ReflectionFactory/ReflectionFactoryTest.java @@ -0,0 +1,341 @@ +/* + * 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(); + } +} diff --git a/test/sun/reflect/ReflectionFactory/security.policy b/test/sun/reflect/ReflectionFactory/security.policy new file mode 100644 index 0000000000000000000000000000000000000000..8c30f3b278c5913d641cefba5de0805a052e6db8 --- /dev/null +++ b/test/sun/reflect/ReflectionFactory/security.policy @@ -0,0 +1,11 @@ +// Individual Permissions for ReflectionFactoryTest +grant { + // Permissions needed to run the test + permission java.util.PropertyPermission "*", "read"; + permission java.io.FilePermission "<>", "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"; +};