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..c5f52cdc6 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,31 @@ 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; + + /** + * The method modifiers a generated proxy keeps when re-declaring a method of the proxied class; + * everything else (e.g. ABSTRACT, SYNCHRONIZED, NATIVE) must not appear on a generated + * delegation method. ACC_BRIDGE / ACC_SYNTHETIC are preserved so that overload resolution + * (e.g. jakarta.el MethodExpressions on proxied beans) can still detect re-declared JVM bridge + * methods via {@link Method#isBridge()}. + */ + public static final int PROXYABLE_METHOD_MODIFIERS = + Modifier.PUBLIC | Modifier.PROTECTED | MODIFIER_VARARGS | MODIFIER_BRIDGE | MODIFIER_SYNTHETIC; + protected final Unsafe unsafe; private final DefiningClassService definingService; diff --git a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java index 8defa2279..2fa944d48 100644 --- a/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java +++ b/webbeans-impl/src/main/java/org/apache/webbeans/proxy/InterceptorDecoratorProxyFactory.java @@ -355,7 +355,7 @@ protected void delegateNonInterceptedMethods(ClassLoader classLoader, ClassWrite exceptionTypeNames[i] = Type.getType(exceptionTypes[i]).getInternalName(); } - int targetModifiers = modifiers & (Modifier.PROTECTED | Modifier.PUBLIC | MODIFIER_VARARGS); + int targetModifiers = modifiers & PROXYABLE_METHOD_MODIFIERS; MethodVisitor mv = cw.visitMethod(targetModifiers, delegatedMethod.getName(), methodDescriptor, null, exceptionTypeNames); @@ -423,7 +423,7 @@ private void generateInterceptorHandledMethod(ClassWriter cw, Method method, int } // push the method definition - int modifier = modifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_VARARGS); + int modifier = modifiers & PROXYABLE_METHOD_MODIFIERS; MethodVisitor mv = cw.visitMethod(modifier, method.getName(), Type.getMethodDescriptor(method), null, null); mv.visitCode(); 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..f31ddc319 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,7 @@ protected void delegateNonInterceptedMethods(ClassLoader classLoader, ClassWrite exceptionTypeNames[i] = Type.getType(exceptionTypes[i]).getInternalName(); } - int targetModifiers = delegatedMethod.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC | MODIFIER_VARARGS); + int targetModifiers = delegatedMethod.getModifiers() & PROXYABLE_METHOD_MODIFIERS; MethodVisitor mv = cw.visitMethod(targetModifiers, delegatedMethod.getName(), methodDescriptor, null, exceptionTypeNames); @@ -454,7 +454,7 @@ private void generateDelegationMethod(ClassWriter cw, Method method, int methodI int modifiers = method.getModifiers(); // push the method definition - int modifier = modifiers & (Opcodes.ACC_PUBLIC | Opcodes.ACC_PROTECTED | Opcodes.ACC_VARARGS); + int modifier = modifiers & PROXYABLE_METHOD_MODIFIERS; 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. + *
+ * {@link SpecificParameterClass} implements {@code GenericParameterInterface<CustomType>},
+ * 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
+ * without 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]);
+
+ Classconsume(CustomBaseType) generated by javac.
+ */
+public interface GenericParameterInterfaceconsume:
+ * the declared consume(CustomType) and the javac-generated
+ * JVM bridge method consume(CustomBaseType).
+ */
+public class SpecificParameterClass implements GenericParameterInterface