diff --git a/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java new file mode 100644 index 00000000000..ee3e42a943c --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2025, 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.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.UndeclaredThrowableException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8370839 + * @summary Behavior of bridge methods in interfaces + * @run junit BridgeMethodsTest + */ +public class BridgeMethodsTest { + + interface StringCallable extends Callable { + @Override + String call(); // throws no exception + } + + @Test + void testExceptionTypes() throws Throwable { + class MyException extends Exception {} + // This proxy has two distinct methods, even though the + // Java language would treat the first one as overridden: + // Object call() throws Exception; - from Callable + // String call(); - from StringCallable + var instance = Proxy.newProxyInstance(StringCallable.class.getClassLoader(), + new Class[] { StringCallable.class }, (_, _, _) -> { throw new MyException(); }); + // The exception can't be thrown through StringCallable.call which has no throws + var undeclared = assertThrows(UndeclaredThrowableException.class, () -> ((StringCallable) instance).call()); + assertInstanceOf(MyException.class, undeclared.getCause()); + // But it can be thrown through Callable.call which permits Exception + assertThrows(MyException.class, () -> ((Callable) instance).call()); + } + + interface SpecificConsumer extends Consumer { + @Override + void accept(String s); + } + + @Test + @SuppressWarnings("unchecked") + void testMethodObjects() throws Throwable { + List methods = new ArrayList<>(); + // This proxy has two distinct methods, even though the + // Java language would treat the first one as overridden: + // void accept(Object); - from Consumer + // void accept(String); - from SpecificConsumer + var instance = Proxy.newProxyInstance(SpecificConsumer.class.getClassLoader(), + new Class[] { SpecificConsumer.class }, (_, m, _) -> methods.add(m)); + ((Consumer) instance).accept(null); + ((SpecificConsumer) instance).accept(null); + assertEquals(2, methods.size()); + // invocation handler gets different method due to covariant parameter types + assertNotEquals(methods.getFirst(), methods.getLast()); + } +} diff --git a/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java b/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java index 8919dc2f0b2..eb455495bf2 100644 --- a/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java +++ b/test/jdk/java/lang/reflect/Proxy/NonPublicMethodTypeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, 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 @@ -21,39 +21,86 @@ * questions. */ -/* - * @test - * @bug 8333854 - * @summary Test invoking a method in a proxy interface with package-private - * classes or interfaces in its method type - * @run junit NonPublicMethodTypeTest - */ +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicReference; import org.junit.jupiter.api.Test; -import java.lang.reflect.Proxy; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertNotSame; +/* + * @test + * @bug 8333854 8370839 + * @summary Behavior of methods whose signature has package-private + * class or interfaces but the proxy interface is public + * @run junit NonPublicMethodTypeTest + */ +public class NonPublicMethodTypeTest { + // Java language and JVM allow using fields and methods with inaccessible + // classes or interfaces in its signature, as long as the field or method + // is accessible and its declaring class or interface is accessible. + // Such inaccessible classes and interfaces are treated as if an arbitrary + // subtype of their accessible types, or an arbitrary supertype of their + // accessible subtypes. + // java.lang.invoke is stricter - MethodType constant pool entry resolution + // for such signatures fail, so they can't be used for MethodHandle or indy. + enum Internal { INSTANCE } -public final class NonPublicMethodTypeTest { - interface NonPublicWorker { - void work(); - } - - public interface PublicWorkable { - void accept(NonPublicWorker worker); + public interface InternalParameter { + void call(Internal parameter); } @Test - public void test() { - PublicWorkable proxy = (PublicWorkable) Proxy.newProxyInstance( - NonPublicMethodTypeTest.class.getClassLoader(), - new Class[] {PublicWorkable.class}, - (_, _, _) -> null); - assertNotSame(NonPublicWorker.class.getPackage(), - proxy.getClass().getPackage(), + void testNonPublicParameter() throws Throwable { + // Creation should be successful + // 8333854 - BSM usage fails for looking up such methods + InternalParameter instance = (InternalParameter) Proxy.newProxyInstance( + InternalParameter.class.getClassLoader(), + new Class[] { InternalParameter.class }, + (_, _, _) -> null); + assertNotSame(Internal.class.getPackage(), + instance.getClass().getPackage(), "Proxy class should not be able to access method parameter " + - "NonPublic type's package"); - proxy.accept(() -> {}); // Call should not fail + "Internal class's package"); + // Calls should be always successful + instance.call(null); + instance.call(Internal.INSTANCE); + } + + public interface InternalReturn { + Internal call(); + } + + @Test + void testNonPublicReturn() throws Throwable { + AtomicReference returnValue = new AtomicReference<>(); + // Creation should be successful + // A lot of annotation interfaces are implemented by such proxy classes, + // due to presence of package-private annotation interface or enum-typed + // elements in public annotation interfaces. + InternalReturn instance = (InternalReturn) Proxy.newProxyInstance( + InternalReturn.class.getClassLoader(), + new Class[] { InternalReturn.class }, + (_, _, _) -> returnValue.get()); + assertNotSame(Internal.class.getPackage(), + instance.getClass().getPackage(), + "Proxy class should not be able to access method parameter " + + "Internal class's package"); + + // The generated call() implementation is as follows: + // aload0, getfield Proxy.h, aload 0, getstatic (Method), aconst_null, + // invokevirtual InvocationHandler::invoke(Object, Method, Object[])Object, + // checkcast Internal.class, areturn + // In this bytecode, checkcast Internal.class will fail with a + // IllegalAccessError as a result of resolution of Internal.class + // if and only if the incoming reference is non-null. + + // checkcast does not perform access check for null + returnValue.set(null); + instance.call(); + // checkcast fails - proxy class cannot access the return type + // See JDK-8349716 + returnValue.set(Internal.INSTANCE); + assertThrows(IllegalAccessError.class, instance::call); } } diff --git a/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java new file mode 100644 index 00000000000..ec01c58b8f0 --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2025, 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.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Proxy; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8370839 + * @summary Behavior of protected methods in java.lang.Object + * @modules java.base/java.lang:+open + * @run junit ProtectedObjectMethodsTest + */ +public class ProtectedObjectMethodsTest { + + static final MethodHandle OBJECT_CLONE; + static final MethodHandle OBJECT_FINALIZE; + + static { + try { + var lookup = MethodHandles.privateLookupIn(Object.class, MethodHandles.lookup()); + OBJECT_CLONE = lookup.findVirtual(Object.class, "clone", MethodType.methodType(Object.class)); + OBJECT_FINALIZE = lookup.findVirtual(Object.class, "finalize", MethodType.methodType(void.class)); + } catch (ReflectiveOperationException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + interface FakeClone { + // This method is not related to Object::clone in the JVM, but most + // implementations in the Java Language override Object::clone as + // covariant overrides + FakeClone clone(); + } + + interface TrueClone { + // This method is identical to Object::clone in the JVM, that calls + // to Object::clone can always call an implementation of this method + // if it exists + Object clone(); + } + + interface PrimitiveClone { + // This method is not related to Object::clone in the JVM, but it can't + // be implemented in the Java Language unless using a lambda expression, + // due to the Java language covariant override restrictions. + int clone(); + } + + // Does not duplicate with Object::clone so it is not proxied + @Test + void testDistinctClone() throws Throwable { + { + // This proxy declares FakeClone clone(); which cannot be + // invoked through Object::clone. + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + // Verify the default Object::clone behavior (this is not Cloneable) + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) fake); + }); + } + + { + // This proxy declares FakeClone clone(); which cannot be + // invoked through Object::clone. + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class, Cloneable.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + // Verify the default Object::clone behavior (this is Cloneable) + var fakeClone = (Object) OBJECT_CLONE.invoke((Object) fake); + assertNotSame(fake, fakeClone); + assertSame(fake.getClass(), fakeClone.getClass()); + assertSame(fakeClone, ((FakeClone) fakeClone).clone()); + } + + { + // This proxy declares int clone(); which cannot be + // invoked through Object::clone. + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class }, (_, _, _) -> 42); + assertEquals(42, instance.clone()); + // Verify the default Object::clone behavior (this is not Cloneable) + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) instance); + }); + } + + { + // This proxy declares int clone(); which cannot be + // invoked through Object::clone + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class, Cloneable.class }, (_, _, _) -> 76); + assertEquals(76, instance.clone()); + // Verify the default Object::clone behavior (this is Cloneable) + var clone = (Object) OBJECT_CLONE.invoke((Object) instance); + assertNotSame(instance, clone); + assertSame(instance.getClass(), clone.getClass()); + assertEquals(76, ((PrimitiveClone) clone).clone()); + } + } + + // Duplicates with Object::clone so it is proxied + @Test + void testDuplicateClone() throws Throwable { + { + // This proxy declares Object clone();, which + // accidentally overrides Object::clone + var instance = (TrueClone) Proxy.newProxyInstance(TrueClone.class.getClassLoader(), new Class[] { TrueClone.class }, (p, _, _) -> p); + assertSame(instance, instance.clone()); + // Verify Object::clone is overridden + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + + { + // This proxy declares Object clone(); and FakeClone clone();. + // They are considered duplicate methods and dispatched equivalently. + // Object clone() accidentally overrides Object::clone, so now + // FakeClone clone() can be called through Object::clone + var instance = Proxy.newProxyInstance(TrueClone.class.getClassLoader(), new Class[] { TrueClone.class, FakeClone.class }, (p, _, _) -> p); + assertSame(instance, ((FakeClone) instance).clone()); + assertSame(instance, ((TrueClone) instance).clone()); + // Verify Object::clone is overridden + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + } + + interface FalseFinalize { + // This method is not related to Object::finalize in the JVM, but it can't + // be implemented in the Java Language unless using a lambda expression, + // due to the Java language covariant override restrictions. + int finalize(); + } + + interface TrueFinalize { + // This method is identical to Object::finalize in the JVM, that calls + // to Object::finalize can always call an implementation of this method + // if it exists + void finalize(); + } + + @Test + void testDistinctFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + // This proxy declares int finalize(), which cannot be + // invoked through Object::finalize. + var instance = Proxy.newProxyInstance(FalseFinalize.class.getClassLoader(), new Class[] { FalseFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + // Verify the default Object::finalize behavior + OBJECT_FINALIZE.invoke(instance); + assertEquals(0, invokeCount.get()); + assertEquals(1, ((FalseFinalize) instance).finalize()); + } + + @Test + void testDuplicateFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + // This proxy declares void finalize(), which can be + // invoked through Object::finalize. + var instance = Proxy.newProxyInstance(TrueFinalize.class.getClassLoader(), new Class[] { TrueFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + // Verify the overridden Object::finalize behavior + OBJECT_FINALIZE.invoke(instance); + assertEquals(1, invokeCount.get()); + ((TrueFinalize) instance).finalize(); + assertEquals(2, invokeCount.get()); + } +}