This is an automated email from the ASF dual-hosted git repository.

benweidig pushed a commit to branch property-expression-parser
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git

commit 98e909b112140bfaa5db91e5ccb66a98abe8b47a
Author: Ben Weidig <[email protected]>
AuthorDate: Sun Oct 25 12:58:44 2026 +0100

    TAP5-2842: Prefer declared methods over bridge methods in property 
expressions
    
    A method invocation used the first method of Class.getMethods() with a
    matching name and parameter count. That order is unspecified, so a
    method implementing a generic interface could resolve to its bridge
    method, and the conduit got the erased type Object instead of the
    declared return type.
    
    A bridge method is now used only when nothing else matches. A public
    method inherited from a non-public class is reachable through a bridge
    method alone.
---
 .../services/PropertyConduitSourceImpl.java        | 16 +++-
 .../tapestry5/beanmodel/BridgeMethodTest.java      | 87 ++++++++++++++++++++++
 2 files changed, 102 insertions(+), 1 deletion(-)

diff --git 
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
 
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
index 7b2e13682..e7ceb5eb5 100644
--- 
a/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
+++ 
b/beanmodel/src/main/java/org/apache/tapestry5/beanmodel/internal/services/PropertyConduitSourceImpl.java
@@ -1310,14 +1310,28 @@ public class PropertyConduitSourceImpl implements 
PropertyConduitSource
 
             while (true)
             {
+                // getMethods() returns them in no particular order. A bridge 
method has an erased return type,
+                // so it is used only when nothing else matches: javac makes a 
public method inherited from a
+                // non-public class reachable through a bridge alone.
+
+                Method bridge = null;
 
                 for (Method method : searchType.getMethods())
                 {
                     if (method.getParameterTypes().length == parameterCount
                             && method.getName().equalsIgnoreCase(methodName))
-                        return method;
+                    {
+                        if (!method.isBridge())
+                            return method;
+
+                        if (bridge == null)
+                            bridge = method;
+                    }
                 }
 
+                if (bridge != null)
+                    return bridge;
+
                 // TAP5-330
                 if (searchType != Object.class)
                 {
diff --git 
a/beanmodel/src/test/java/org/apache/tapestry5/beanmodel/BridgeMethodTest.java 
b/beanmodel/src/test/java/org/apache/tapestry5/beanmodel/BridgeMethodTest.java
new file mode 100644
index 000000000..93616a355
--- /dev/null
+++ 
b/beanmodel/src/test/java/org/apache/tapestry5/beanmodel/BridgeMethodTest.java
@@ -0,0 +1,87 @@
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package org.apache.tapestry5.beanmodel;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import org.apache.tapestry5.beanmodel.services.PropertyConduitSource;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * A method invocation resolves to the declared method, not to a 
compiler-generated bridge method,
+ * whatever order {@link Class#getMethods()} returns them in.
+ */
+public class BridgeMethodTest
+{
+    private static PropertyConduitSource source;
+
+    @BeforeAll
+    static void setup()
+    {
+        source = PropertyConduitSourceFixture.create();
+    }
+
+    @Test
+    void generic_interface_method_has_the_resolved_return_type()
+    {
+        assertSame(Long.class, source.create(LongProducer.class, 
"produce()").getPropertyType());
+    }
+
+    @Test
+    void generic_interface_method_is_invoked()
+    {
+        assertEquals(42L, source.create(LongProducer.class, 
"produce()").get(new LongProducer()));
+    }
+
+    @Test
+    void method_inherited_from_package_private_class_is_invoked()
+    {
+        // javac makes the inherited method reachable only through a bridge 
method in the public subclass.
+
+        assertEquals("base", source.create(PublicSubclass.class, 
"getValue()").get(new PublicSubclass()));
+    }
+
+    @Test
+    void method_inherited_from_package_private_class_has_its_return_type()
+    {
+        assertSame(String.class, source.create(PublicSubclass.class, 
"getValue()").getPropertyType());
+    }
+
+    public interface Producer<T>
+    {
+        T produce();
+    }
+
+    public static class LongProducer implements Producer<Long>
+    {
+        @Override
+        public Long produce()
+        {
+            return 42L;
+        }
+    }
+
+    static class PackagePrivateBase
+    {
+        public String getValue()
+        {
+            return "base";
+        }
+    }
+
+    public static class PublicSubclass extends PackagePrivateBase
+    {
+    }
+}

Reply via email to