This is an automated email from the ASF dual-hosted git repository. rmaucher pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 1c545e490297673d7d1b4b27ba18f6255c8c821b Author: opencode <[email protected]> AuthorDate: Sun Oct 18 15:17:41 2026 +0200 Report inaccessible methods in BeanELResolver as MethodNotFoundException When a method call in an EL value expression was resolved by BeanELResolver, the search for a matching method could legitimately complete without finding an accessible method. A method with a matching name and signature exists but is treated as not found when the class that defines it is not public and the method is not exposed via a public interface or a public super class. The resolver then used the null result to build the call parameters, producing a raw NullPointerException. A valid expression such as calling a public method on a package-private bean reported an unexpected container error rather than the exception the EL API requires. Check for the null result and report a MethodNotFoundException with a message naming the method and the class. This matches how the sibling static method invocation in StaticFieldELResolver and the constructor lookup in Util already handle the same situation. The method expression path in org.apache.el already reported this case as a MethodNotFoundException and required no change. Adds tests for the no-argument and argument paths of a method that is only accessible on a non-public class, and for the unchanged case of a method exposed via a public interface. The first two report the raw NullPointerException against the previous implementation. --- java/javax/el/BeanELResolver.java | 8 +++++ java/javax/el/LocalStrings.properties | 2 ++ test/javax/el/TestBeanELResolver.java | 66 +++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/java/javax/el/BeanELResolver.java b/java/javax/el/BeanELResolver.java index 38f74081f6..98ab4a738d 100644 --- a/java/javax/el/BeanELResolver.java +++ b/java/javax/el/BeanELResolver.java @@ -144,6 +144,14 @@ public class BeanELResolver extends ELResolver { // Find the matching method Method matchingMethod = Util.findMethod(context, base.getClass(), base, methodName, paramTypes, params); + // Util.findMethod() returns null when no accessible method was found, + // e.g. when the method exists but is not publicly accessible because + // the class that defines it is not public. + if (matchingMethod == null) { + throw new MethodNotFoundException( + Util.message(context, "beanELResolver.methodNotFound", methodName, base.getClass().getName())); + } + Object[] parameters = Util.buildParameters(context, matchingMethod.getParameterTypes(), matchingMethod.isVarArgs(), params); diff --git a/java/javax/el/LocalStrings.properties b/java/javax/el/LocalStrings.properties index d3bfe94fbe..44005474d7 100644 --- a/java/javax/el/LocalStrings.properties +++ b/java/javax/el/LocalStrings.properties @@ -16,6 +16,8 @@ # Do not edit this file directly. # To edit translations see: https://tomcat.apache.org/getinvolved.html#Translations +beanELResolver.methodNotFound=No matching public method named [{0}] found on class [{1}] + beanNameELResolver.beanReadOnly=The bean name [{0}] is read-only elProcessor.defineFunctionInvalidClass=The class [{0}] is not public diff --git a/test/javax/el/TestBeanELResolver.java b/test/javax/el/TestBeanELResolver.java index 87073dfc58..6f60e969c5 100644 --- a/test/javax/el/TestBeanELResolver.java +++ b/test/javax/el/TestBeanELResolver.java @@ -447,6 +447,48 @@ public class TestBeanELResolver { resolver.invoke(context, new TesterBean(BEAN_NAME), METHOD03_NAME, new Class<?>[] {}, new Object[] {}); } + /** + * Tests that a call to a public method on a non-public class, where the + * method is not exposed via a public interface or super class, throws a + * MethodNotFoundException rather than producing a NullPointerException + * from the use of the null result of the method search. + */ + @Test(expected = MethodNotFoundException.class) + public void testInvokeNonPublicClass01() { + BeanELResolver resolver = new BeanELResolver(); + ELContext context = new StandardELContext(ELManager.getExpressionFactory()); + + resolver.invoke(context, new NonPublicBean(), "getName", new Class<?>[] {}, new Object[] {}); + } + + /** + * Tests the same behaviour as testInvokeNonPublicClass01() for a method + * with parameters, which is resolved via the slower, broader logic. + */ + @Test(expected = MethodNotFoundException.class) + public void testInvokeNonPublicClass02() { + BeanELResolver resolver = new BeanELResolver(); + ELContext context = new StandardELContext(ELManager.getExpressionFactory()); + + resolver.invoke(context, new NonPublicBean(), "echo", new Class<?>[] { String.class }, + new Object[] { "value" }); + } + + /** + * Tests that a public method on a non-public class is invoked normally + * when it is exposed via a public interface. + */ + @Test + public void testInvokeNonPublicClass03() { + BeanELResolver resolver = new BeanELResolver(); + ELContext context = new StandardELContext(ELManager.getExpressionFactory()); + + Object result = resolver.invoke(context, new Bean(), "getValueC", new Class<?>[] {}, new Object[] {}); + + Assert.assertEquals("Default", result); + Assert.assertTrue(context.isPropertyResolved()); + } + @Test public void testInvokeVarargsCoerce01() { BeanELResolver resolver = new BeanELResolver(); @@ -946,6 +988,30 @@ public class TestBeanELResolver { } } + + /* + * Deliberately not public. The methods are only accessible via this class + * so, for the purposes of the EL, they are not accessible at all. + */ + private static class NonPublicBean { + + public String getName() { + return BEAN_NAME; + } + + public String echo(String value) { + return value; + } + } + + + public interface MyInterface { + default String getValueC() { + return "Default"; + } + } + + private void doNegativeTest(Object base, Object trigger, MethodUnderTest method, boolean checkResult) { BeanELResolver resolver = new BeanELResolver(); ELContext context = new StandardELContext(ELManager.getExpressionFactory()); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
