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

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

commit 9b8699badf9c4488796eea7a1f7c0eaf63c7fce7
Author: opencode <[email protected]>
AuthorDate: Wed Oct 14 17:14:02 2026 +0200

    Emit a null literal for missing CallMethodRule code-gen parameters
    
    A CallMethodRule collecting two or more parameters passes a parameter
    whose nested element is absent to the target method as a null value.
    That is documented behaviour, and the method call itself handles it.
    
    The code generation block at the end of end() did not. A null parameter
    is not a String, so it was handed to Digester.toVariableName() to name a
    variable for it. That method registers the object and then dereferences
    it, so the generated code path aborted the parse with a
    NullPointerException, wrapped in a SAXException, as soon as a
    multi-parameter element omitted one of its parameter children, for
    example an init-param without an init-value. This affects the opt-in
    -generateCode path used to pre-compile descriptor processing.
    
    Emit the null literal for a null parameter instead. The generated
    statement then reflects the call that is actually made. All first-party
    targets of these rules declare a single method of that name, so the
    generated source resolves to the same method the reflective invocation
    selected; a cast is not needed and would require canonicalising type
    names for no first-party benefit.
---
 .../tomcat/util/digester/CallMethodRule.java       |  7 ++
 .../tomcat/util/digester/TestCallMethodRule.java   | 98 ++++++++++++++++++++++
 2 files changed, 105 insertions(+)

diff --git a/java/org/apache/tomcat/util/digester/CallMethodRule.java 
b/java/org/apache/tomcat/util/digester/CallMethodRule.java
index c475df0ebe..1f2cb4f37f 100644
--- a/java/org/apache/tomcat/util/digester/CallMethodRule.java
+++ b/java/org/apache/tomcat/util/digester/CallMethodRule.java
@@ -352,6 +352,13 @@ public class CallMethodRule extends Rule {
                 }
                 if (bodyText != null) {
                     
code.append("\"").append(IntrospectionUtils.escape(bodyText)).append("\"");
+                } else if (paramValues[i] == null) {
+                    // Missing parameters are passed to the method as null
+                    // values. Emit the literal rather than trying to name a
+                    // variable for an object that does not exist. The
+                    // generated call assumes the target method resolves
+                    // unambiguously for a null argument.
+                    code.append("null");
                 } else if (paramValues[i] instanceof String) {
                     
code.append("\"").append(IntrospectionUtils.escape(paramValues[i].toString())).append("\"");
                 } else {
diff --git a/test/org/apache/tomcat/util/digester/TestCallMethodRule.java 
b/test/org/apache/tomcat/util/digester/TestCallMethodRule.java
new file mode 100644
index 0000000000..43510aff83
--- /dev/null
+++ b/test/org/apache/tomcat/util/digester/TestCallMethodRule.java
@@ -0,0 +1,98 @@
+/*
+ * 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.tomcat.util.digester;
+
+import java.io.StringReader;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.xml.sax.InputSource;
+
+public class TestCallMethodRule {
+
+    public static class TwoArgBean {
+
+        private String first;
+        private String second;
+        private int calls;
+
+        public void setTwo(String first, String second) {
+            this.first = first;
+            this.second = second;
+            calls++;
+        }
+
+        public String getFirst() {
+            return first;
+        }
+
+        public String getSecond() {
+            return second;
+        }
+
+        public int getCalls() {
+            return calls;
+        }
+    }
+
+
+    private static Digester createDigester() {
+        Digester digester = new Digester();
+        digester.addObjectCreate("root", TwoArgBean.class.getName());
+        digester.addCallMethod("root/call", "setTwo", 2);
+        // Only the first parameter has a nested element. The second one is
+        // legitimately missing and is passed to the method as null.
+        digester.addCallParam("root/call/first", 0);
+        return digester;
+    }
+
+
+    @Test
+    public void testMissingParameterIsPassedAsNull() throws Exception {
+        Digester digester = createDigester();
+
+        TwoArgBean bean = (TwoArgBean) digester
+                .parse(new InputSource(new 
StringReader("<root><call><first>x</first></call></root>")));
+
+        Assert.assertEquals(1, bean.getCalls());
+        Assert.assertEquals("x", bean.getFirst());
+        Assert.assertNull(bean.getSecond());
+    }
+
+
+    /*
+     * Code generation must render a missing (null) parameter as the null
+     * literal rather than generating a variable name for it, which previously
+     * failed with a NullPointerException.
+     */
+    @Test
+    public void testGenerateCodeWithMissingParameter() throws Exception {
+        Digester digester = createDigester();
+
+        digester.startGeneratingCode();
+        TwoArgBean bean = (TwoArgBean) digester
+                .parse(new InputSource(new 
StringReader("<root><call><first>x</first></call></root>")));
+        String code = digester.getGeneratedCode().toString();
+        digester.endGeneratingCode();
+
+        Assert.assertEquals(1, bean.getCalls());
+        Assert.assertNull(bean.getSecond());
+        Assert.assertTrue("Generated code was: " + System.lineSeparator() + 
code,
+                code.contains(".setTwo(\"x\", null);"));
+    }
+}


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

Reply via email to