From 5166e23634f5d01f533412d2c6777b9960a007f0 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 28 Oct 2025 11:51:23 -0500 Subject: [PATCH 1/2] 8370839: Tests to verify peculiar Proxy dispatching behaviors --- .../lang/reflect/Proxy/BridgeMethods.java | 79 +++++++++ .../reflect/Proxy/ProtectedObjectMethods.java | 151 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 test/jdk/java/lang/reflect/Proxy/BridgeMethods.java create mode 100644 test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java diff --git a/test/jdk/java/lang/reflect/Proxy/BridgeMethods.java b/test/jdk/java/lang/reflect/Proxy/BridgeMethods.java new file mode 100644 index 0000000000000..69b1affe7688c --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/BridgeMethods.java @@ -0,0 +1,79 @@ +/* + * 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 BridgeMethods + */ +public class BridgeMethods { + + interface StringCallable extends Callable { + @Override + String call(); // throws no exception + } + + // Does not duplicate with Object::clone so it is not proxied + @Test + void testExceptionTypes() throws Throwable { + class MyException extends Exception {} + 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<>(); + 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/ProtectedObjectMethods.java b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java new file mode 100644 index 0000000000000..7985c0918f199 --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java @@ -0,0 +1,151 @@ +/* + * 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 ProtectedObjectMethods + */ +public class ProtectedObjectMethods { + + 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 { + FakeClone clone(); + } + + interface TrueClone { + Object clone(); + } + + interface PrimitiveClone { + int clone(); + } + + // Does not duplicate with Object::clone so it is not proxied + @Test + void testDistinctClone() throws Throwable { + { + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) fake); + }); + } + + { + var fake = (FakeClone) Proxy.newProxyInstance(FakeClone.class.getClassLoader(), new Class[] { FakeClone.class, Cloneable.class }, (p, _, _) -> p); + assertSame(fake, fake.clone()); + var fakeClone = (Object) OBJECT_CLONE.invoke((Object) fake); + assertNotSame(fake, fakeClone); + assertSame(fake.getClass(), fakeClone.getClass()); + assertSame(fakeClone, ((FakeClone) fakeClone).clone()); + } + + { + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class }, (_, _, _) -> 42); + assertEquals(42, instance.clone()); + assertThrows(CloneNotSupportedException.class, () -> { + var _ = (Object) OBJECT_CLONE.invoke((Object) instance); + }); + } + + { + var instance = (PrimitiveClone) Proxy.newProxyInstance(PrimitiveClone.class.getClassLoader(), new Class[] { PrimitiveClone.class, Cloneable.class }, (_, _, _) -> 76); + assertEquals(76, instance.clone()); + 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 { + { + // TrueClone::clone accidentally overrides Object::clone + var instance = (TrueClone) Proxy.newProxyInstance(TrueClone.class.getClassLoader(), new Class[] { TrueClone.class }, (p, _, _) -> p); + assertSame(instance, instance.clone()); + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + + { + // Use the TrueClone to bridge Object::clone and FakeClone::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()); + assertSame(instance, (Object) OBJECT_CLONE.invoke((Object) instance)); + } + } + + interface FalseFinalize { + int finalize(); + } + + interface TrueFinalize { + void finalize(); + } + + @Test + void testDistinctFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + var instance = Proxy.newProxyInstance(FalseFinalize.class.getClassLoader(), new Class[] { FalseFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + OBJECT_FINALIZE.invoke(instance); + assertEquals(0, invokeCount.get()); + assertEquals(1, ((FalseFinalize) instance).finalize()); + } + + @Test + void testDuplicateFinalize() throws Throwable { + AtomicInteger invokeCount = new AtomicInteger(); + var instance = Proxy.newProxyInstance(TrueFinalize.class.getClassLoader(), new Class[] { TrueFinalize.class }, (_, _, _) -> invokeCount.incrementAndGet()); + OBJECT_FINALIZE.invoke(instance); + assertEquals(1, invokeCount.get()); + ((TrueFinalize) instance).finalize(); + assertEquals(2, invokeCount.get()); + } +} From 9ab03f7740c4167e863e70a33fd253e3f94e0112 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 28 Oct 2025 13:20:28 -0500 Subject: [PATCH 2/2] Tweaks --- ...dgeMethods.java => BridgeMethodsTest.java} | 4 +- .../Proxy/NonPublicSignaturesTest.java | 75 +++++++++++++++++++ ...s.java => ProtectedObjectMethodsTest.java} | 4 +- 3 files changed, 79 insertions(+), 4 deletions(-) rename test/jdk/java/lang/reflect/Proxy/{BridgeMethods.java => BridgeMethodsTest.java} (97%) create mode 100644 test/jdk/java/lang/reflect/Proxy/NonPublicSignaturesTest.java rename test/jdk/java/lang/reflect/Proxy/{ProtectedObjectMethods.java => ProtectedObjectMethodsTest.java} (98%) diff --git a/test/jdk/java/lang/reflect/Proxy/BridgeMethods.java b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java similarity index 97% rename from test/jdk/java/lang/reflect/Proxy/BridgeMethods.java rename to test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java index 69b1affe7688c..ce62cc8dcc55a 100644 --- a/test/jdk/java/lang/reflect/Proxy/BridgeMethods.java +++ b/test/jdk/java/lang/reflect/Proxy/BridgeMethodsTest.java @@ -37,9 +37,9 @@ * @test * @bug 8370839 * @summary Behavior of bridge methods in interfaces - * @run junit BridgeMethods + * @run junit BridgeMethodsTest */ -public class BridgeMethods { +public class BridgeMethodsTest { interface StringCallable extends Callable { @Override diff --git a/test/jdk/java/lang/reflect/Proxy/NonPublicSignaturesTest.java b/test/jdk/java/lang/reflect/Proxy/NonPublicSignaturesTest.java new file mode 100644 index 0000000000000..d8198a47c01d2 --- /dev/null +++ b/test/jdk/java/lang/reflect/Proxy/NonPublicSignaturesTest.java @@ -0,0 +1,75 @@ +/* + * 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.Proxy; +import java.util.concurrent.atomic.AtomicReference; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8370839 + * @summary Behavior of methods whose signature has package-private + * class or interfaces but the proxy interface is public + * @run junit NonPublicSignaturesTest + */ +public class NonPublicSignaturesTest { + enum Internal { INSTANCE } + + public interface InternalParameter { + void call(Internal parameter); + } + + @Test + void testNonPublicParameter() throws Throwable { + // Creation should be successful + InternalParameter instance = (InternalParameter) Proxy.newProxyInstance( + InternalParameter.class.getClassLoader(), + new Class[] { InternalParameter.class }, + (_, _, _) -> null); + 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 + InternalReturn instance = (InternalReturn) Proxy.newProxyInstance( + InternalReturn.class.getClassLoader(), + new Class[] { InternalReturn.class }, + (_, _, _) -> returnValue.get()); + // checkcast does not perform access check for null + returnValue.set(null); + instance.call(); + // checkcast fails - proxy class cannot access the return type + returnValue.set(Internal.INSTANCE); + assertThrows(IllegalAccessError.class, instance::call); + } +} diff --git a/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java similarity index 98% rename from test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java rename to test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java index 7985c0918f199..6706daf7eb38f 100644 --- a/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethods.java +++ b/test/jdk/java/lang/reflect/Proxy/ProtectedObjectMethodsTest.java @@ -36,9 +36,9 @@ * @bug 8370839 * @summary Behavior of protected methods in java.lang.Object * @modules java.base/java.lang:+open - * @run junit ProtectedObjectMethods + * @run junit ProtectedObjectMethodsTest */ -public class ProtectedObjectMethods { +public class ProtectedObjectMethodsTest { static final MethodHandle OBJECT_CLONE; static final MethodHandle OBJECT_FINALIZE;