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

struberg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/openwebbeans.git

commit 376c0842dd51bdea7ad090842b6a25faf8f20668
Author: Richard Zowalla <[email protected]>
AuthorDate: Wed Jul 8 20:31:16 2026 +0200

    OWB-1465 - Preserve ACC_BRIDGE / ACC_SYNTHETIC on bridge methods 
re-declared by normal scope proxies
    
    NormalScopeProxyFactory re-declares the JVM bridge methods of the
    proxied class but masked the method modifiers with
    PROTECTED | PUBLIC | VARARGS, dropping the ACC_BRIDGE flag. The proxy
    then exposed two regular methods with the same name and assignable
    parameter types, which breaks overload resolution in EL
    implementations (jakarta.el / Tomcat's ReflectionUtil tie-break on
    Method#isBridge()) with 'Unable to find unambiguous method'.
    
    Keep ACC_BRIDGE and ACC_SYNTHETIC in both generation paths
    (delegateNonInterceptedMethods for public methods,
    generateDelegationMethod for protected ones). Introduces
    MODIFIER_BRIDGE / MODIFIER_SYNTHETIC constants next to the existing
    MODIFIER_VARARGS as java.lang.reflect.Modifier does not expose them.
---
 .../webbeans/proxy/AbstractProxyFactory.java       |  15 +++
 .../webbeans/proxy/NormalScopeProxyFactory.java    |  10 +-
 .../factory/ProxyBridgeMethodFlagTest.java         | 132 +++++++++++++++++++++
 .../test/util/GenericParameterInterface.java       |  29 +++++
 .../webbeans/test/util/SpecificParameterClass.java |  34 ++++++
 5 files changed, 218 insertions(+), 2 deletions(-)

diff --git 
a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
 
b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
index 51be6a45b..5dba228bc 100644
--- 
a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
+++ 
b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/AbstractProxyFactory.java
@@ -60,6 +60,21 @@ public abstract class AbstractProxyFactory
      */
     public static final int MODIFIER_VARARGS = 0x00000080;
 
+    /**
+     * This is needed as the Modifier#BRIDGE is not (yet) public. Must be 
preserved when a proxy
+     * re-declares a JVM bridge method: overload resolution e.g. in Expression 
Language
+     * implementations relies on {@link Method#isBridge()} to disambiguate 
overloaded methods.
+     * Note that the bitcode is the same as Modifier#VOLATILE.
+     * But 'bridge' is only for methods, whereas 'volatile' is only for fields.
+     */
+    public static final int MODIFIER_BRIDGE = 0x00000040;
+
+    /**
+     * This is needed as the Modifier#SYNTHETIC is not (yet) public.
+     * JVM bridge methods carry ACC_BRIDGE | ACC_SYNTHETIC, so keep both on 
re-declared methods.
+     */
+    public static final int MODIFIER_SYNTHETIC = 0x00001000;
+
     protected final Unsafe unsafe;
 
     private final DefiningClassService definingService;
diff --git 
a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java
 
b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java
index 4c3256bbf..0756b237d 100644
--- 
a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java
+++ 
b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/NormalScopeProxyFactory.java
@@ -395,7 +395,10 @@ public class NormalScopeProxyFactory extends 
AbstractProxyFactory
                 exceptionTypeNames[i] = 
Type.getType(exceptionTypes[i]).getInternalName();
             }
 
-            int targetModifiers = delegatedMethod.getModifiers() & 
(Modifier.PROTECTED | Modifier.PUBLIC | MODIFIER_VARARGS);
+            // preserve ACC_BRIDGE | ACC_SYNTHETIC on re-declared JVM bridge 
methods: overload resolution
+            // (e.g. jakarta.el MethodExpressions on proxied beans) relies on 
Method#isBridge() to disambiguate
+            int targetModifiers = delegatedMethod.getModifiers()
+                    & (Modifier.PROTECTED | Modifier.PUBLIC | MODIFIER_VARARGS 
| MODIFIER_BRIDGE | MODIFIER_SYNTHETIC);
 
             MethodVisitor mv = cw.visitMethod(targetModifiers, 
delegatedMethod.getName(), methodDescriptor, null, exceptionTypeNames);
 
@@ -454,7 +457,10 @@ public class NormalScopeProxyFactory extends 
AbstractProxyFactory
         int modifiers = method.getModifiers();
 
         // push the method definition
-        int modifier = modifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED 
| Opcodes.ACC_VARARGS);
+        // preserve ACC_BRIDGE | ACC_SYNTHETIC on re-declared JVM bridge 
methods: overload resolution
+        // (e.g. jakarta.el MethodExpressions on proxied beans) relies on 
Method#isBridge() to disambiguate
+        int modifier = modifiers
+                & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | 
Opcodes.ACC_VARARGS | Opcodes.ACC_BRIDGE | Opcodes.ACC_SYNTHETIC);
 
         MethodVisitor mv = cw.visitMethod(modifier, method.getName(), 
Type.getMethodDescriptor(method), null, null);
         mv.visitCode();
diff --git 
a/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/ProxyBridgeMethodFlagTest.java
 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/ProxyBridgeMethodFlagTest.java
new file mode 100644
index 000000000..289233136
--- /dev/null
+++ 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/interceptors/factory/ProxyBridgeMethodFlagTest.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.webbeans.test.interceptors.factory;
+
+import org.apache.webbeans.config.WebBeansContext;
+import org.apache.webbeans.proxy.InterceptorDecoratorProxyFactory;
+import org.apache.webbeans.proxy.NormalScopeProxyFactory;
+import org.apache.webbeans.test.util.CustomBaseType;
+import org.apache.webbeans.test.util.CustomType;
+import org.apache.webbeans.test.util.SpecificParameterClass;
+import org.apache.webbeans.util.ClassUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Proxies which materialize JVM bridge methods must keep the {@code 
ACC_BRIDGE} flag
+ * on the generated method.
+ * <p>
+ * {@link SpecificParameterClass} implements {@code 
GenericParameterInterface&lt;CustomType&gt;},
+ * so javac generates the bridge method {@code consume(CustomBaseType)} next 
to the declared
+ * {@code consume(CustomType)}. When a subclass proxy re-declares that bridge 
method
+ * <em>without</em> the {@code ACC_BRIDGE} flag, the proxy class exposes two 
regular methods
+ * named {@code consume} with assignable parameter types. Expression language 
implementations
+ * resolve overloaded methods by preferring the non-bridge variant (see
+ * {@code jakarta.el}/Tomcat {@code org.apache.el.util.ReflectionUtil}); with 
the flag dropped
+ * the invocation of e.g. {@code #{bean.consume(x)}} fails with
+ * "{@code MethodNotFoundException: Unable to find unambiguous method}".
+ */
+public class ProxyBridgeMethodFlagTest
+{
+
+    @Test
+    public void normalScopeProxyPreservesBridgeMethodFlag() throws Exception
+    {
+        NormalScopeProxyFactory pf = new NormalScopeProxyFactory(new 
WebBeansContext());
+
+        // we take a fresh URLClassLoader to not blur the test classpath with 
synthetic classes.
+        ClassLoader classLoader = new URLClassLoader(new URL[0]);
+
+        Class<SpecificParameterClass> proxyClass = 
pf.createProxyClass(classLoader, SpecificParameterClass.class);
+        Assert.assertNotNull(proxyClass);
+
+        assertBridgeFlagPreserved(proxyClass);
+    }
+
+    /**
+     * Documents the currently correct behaviour of the {@link 
InterceptorDecoratorProxyFactory}:
+     * bridge methods are skipped entirely ({@code unproxyableMethod}), so the 
proxy inherits the
+     * properly flagged bridge method from the proxied superclass. This test 
passes and guards
+     * against regressions; only {@link NormalScopeProxyFactory} re-declares 
bridge methods and
+     * loses the flag.
+     */
+    @Test
+    public void interceptorProxyPreservesBridgeMethodFlag() throws Exception
+    {
+        InterceptorDecoratorProxyFactory pf = new 
InterceptorDecoratorProxyFactory(new WebBeansContext());
+
+        // we take a fresh URLClassLoader to not blur the test classpath with 
synthetic classes.
+        ClassLoader classLoader = new URLClassLoader(new URL[0]);
+
+        List<Method> methods = 
ClassUtil.getNonPrivateMethods(SpecificParameterClass.class, true, true);
+
+        List<Method> interceptedMethods = new ArrayList<>();
+        List<Method> nonInterceptedMethods = new ArrayList<>();
+        for (Method m : methods)
+        {
+            if (m.isBridge())
+            {
+                // bridge methods only get delegated, they are never 
intercepted (OWB-1234)
+                nonInterceptedMethods.add(m);
+            }
+            else
+            {
+                interceptedMethods.add(m);
+            }
+        }
+
+        Class<SpecificParameterClass> proxyClass = pf.createProxyClass(
+                new InterceptorDecoratorProxyFactoryTest.DummyBean(), 
classLoader, SpecificParameterClass.class,
+                interceptedMethods.toArray(new 
Method[interceptedMethods.size()]),
+                nonInterceptedMethods.toArray(new 
Method[nonInterceptedMethods.size()]));
+        Assert.assertNotNull(proxyClass);
+
+        assertBridgeFlagPreserved(proxyClass);
+    }
+
+    private void assertBridgeFlagPreserved(Class<? extends 
SpecificParameterClass> proxyClass) throws Exception
+    {
+        // sanity check the fixture: javac generated the bridge method on the 
proxied class
+        Method originalBridge = 
SpecificParameterClass.class.getDeclaredMethod("consume", CustomBaseType.class);
+        Assert.assertTrue(originalBridge.isBridge());
+        Method originalSpecific = 
SpecificParameterClass.class.getDeclaredMethod("consume", CustomType.class);
+        Assert.assertFalse(originalSpecific.isBridge());
+
+        // exactly like on the proxied class itself, only ONE non-bridge 
consume method may be visible
+        List<Method> nonBridgeMethods = new ArrayList<>();
+        for (Method m : proxyClass.getMethods())
+        {
+            if ("consume".equals(m.getName()) && m.getParameterCount() == 1 && 
!m.isBridge())
+            {
+                nonBridgeMethods.add(m);
+            }
+        }
+
+        Assert.assertEquals("the proxy must not expose the JVM bridge method 
consume(CustomBaseType) as a"
+                + " regular method - overload resolution (e.g. jakarta.el 
MethodExpressions on proxied beans)"
+                + " relies on the ACC_BRIDGE flag to disambiguate, but found: 
" + nonBridgeMethods,
+                1, nonBridgeMethods.size());
+    }
+}
diff --git 
a/webbeans-impl/src/test/java/org/apache/webbeans/test/util/GenericParameterInterface.java
 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/util/GenericParameterInterface.java
new file mode 100644
index 000000000..b199c8cfb
--- /dev/null
+++ 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/util/GenericParameterInterface.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.webbeans.test.util;
+
+/**
+ * A generic interface with a type-variable method <em>parameter</em>.
+ * Implementations with a concrete type argument get a JVM bridge method
+ * <code>consume(CustomBaseType)</code> generated by javac.
+ */
+public interface GenericParameterInterface<T extends CustomBaseType>
+{
+    String consume(T instance);
+}
diff --git 
a/webbeans-impl/src/test/java/org/apache/webbeans/test/util/SpecificParameterClass.java
 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/util/SpecificParameterClass.java
new file mode 100644
index 000000000..4f8213351
--- /dev/null
+++ 
b/webbeans-impl/src/test/java/org/apache/webbeans/test/util/SpecificParameterClass.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.webbeans.test.util;
+
+/**
+ * Implements {@link GenericParameterInterface} with a concrete type argument.
+ * The compiled class therefore contains two methods named 
<code>consume</code>:
+ * the declared <code>consume(CustomType)</code> and the javac-generated
+ * JVM bridge method <code>consume(CustomBaseType)</code>.
+ */
+public class SpecificParameterClass implements 
GenericParameterInterface<CustomType>
+{
+    @Override
+    public String consume(CustomType instance)
+    {
+        return "specific";
+    }
+}

Reply via email to