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

rmaucher pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git

commit 8a1ac3c5e9e80682424edf99a7cd623fe83257b2
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/jakarta/el/BeanELResolver.java     |  8 +++++
 java/jakarta/el/LocalStrings.properties |  2 ++
 test/jakarta/el/TestBeanELResolver.java | 58 +++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+)

diff --git a/java/jakarta/el/BeanELResolver.java 
b/java/jakarta/el/BeanELResolver.java
index 0d8ad14558..69b45c6662 100644
--- a/java/jakarta/el/BeanELResolver.java
+++ b/java/jakarta/el/BeanELResolver.java
@@ -150,6 +150,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/jakarta/el/LocalStrings.properties 
b/java/jakarta/el/LocalStrings.properties
index 525f92b919..a1b85ad2e0 100644
--- a/java/jakarta/el/LocalStrings.properties
+++ b/java/jakarta/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/jakarta/el/TestBeanELResolver.java 
b/test/jakarta/el/TestBeanELResolver.java
index 69ea92aaf1..6f58e2ac13 100644
--- a/test/jakarta/el/TestBeanELResolver.java
+++ b/test/jakarta/el/TestBeanELResolver.java
@@ -453,6 +453,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();
@@ -968,6 +1010,22 @@ 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";


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to