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 6009853c4d892f14db563f7e3153d52336cf0ae3
Author: opencode <[email protected]>
AuthorDate: Mon Oct 19 15:14:04 2026 +0200

    Store the Set tag plugin deferred value in the variable mapper
    
    When the value attribute of c:set with a var is a deferred expression
    (#{...}), the generated code hands the tag a value expression object
    rather than a value, as required by the deferred-value declaration of
    the tag. The plugin stored that object itself as the scoped attribute,
    so referencing the variable rendered the toString of the expression
    object instead of its lazily evaluated result, and setting the variable
    to null could not remove it. It also accepted a deferred value in any
    scope, while the standard handler requires the page scope for one.
    
    The var branch now mirrors the handler: a value expression is exported
    through the variable mapper of the EL context, any other scope is
    rejected for it, and mapper entries are removed when a plain value or a
    null takes the place of a deferred one, so no mapping shadows the
    attribute that is set or removed instead.
---
 .../apache/jasper/tagplugins/jstl/core/Set.java    | 30 +++++++++++++++--
 .../jasper/tagplugins/jstl/core/TestSet.java       | 38 +++++++++++++++++++++
 test/webapp/jstl/set-deferred.jsp                  | 39 ++++++++++++++++++++++
 webapps/docs/changelog.xml                         | 11 ++++++
 4 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/java/org/apache/jasper/tagplugins/jstl/core/Set.java 
b/java/org/apache/jasper/tagplugins/jstl/core/Set.java
index 7b12161778..f04457ad83 100644
--- a/java/org/apache/jasper/tagplugins/jstl/core/Set.java
+++ b/java/org/apache/jasper/tagplugins/jstl/core/Set.java
@@ -16,6 +16,8 @@
  */
 package org.apache.jasper.tagplugins.jstl.core;
 
+import jakarta.servlet.jsp.PageContext;
+
 import org.apache.jasper.compiler.tagplugin.TagPlugin;
 import org.apache.jasper.compiler.tagplugin.TagPluginContext;
 import org.apache.jasper.tagplugins.jstl.Util;
@@ -87,10 +89,32 @@ public class Set implements TagPlugin {
         // if the attribute var has been specified then assign the result to 
the var;
         if (hasVar) {
             String strVar = ctxt.getConstantAttribute("var");
-            ctxt.generateJavaSource("if(null != " + resultName + "){");
-            ctxt.generateJavaSource(
-                    "    " + jspCtxt + ".setAttribute(\"" + strVar + "\"," + 
resultName + "," + iScope + ");");
+            String varMapper = jspCtxt + ".getELContext().getVariableMapper()";
+            // A deferred value is passed as a value expression and is then
+            // exported as a mapping in the variable mapper, as the standard
+            // handler does; only the page scope can hold such a mapping.
+            ctxt.generateJavaSource("if(" + resultName +
+                    " instanceof jakarta.el.ValueExpression) {");
+            if (iScope == PageContext.PAGE_SCOPE) {
+                ctxt.generateJavaSource("    " + varMapper + ".setVariable(\"" 
+ strVar +
+                        "\", (jakarta.el.ValueExpression) " + resultName + 
");");
+            } else {
+                ctxt.generateJavaSource("    throw new 
JspTagException(\"Invalid scope for ValueExpression in " +
+                        "&lt;set&gt;. \\\"page\\\" scope is required but was 
\\\"" + strScope + "\\\"\");");
+            }
+            ctxt.generateJavaSource("} else if(null != " + resultName + "){");
+            // A mapping in the variable mapper would shadow the attribute
+            if (iScope == PageContext.PAGE_SCOPE) {
+                ctxt.generateJavaSource("    if(" + varMapper + 
".resolveVariable(\"" + strVar + "\") != null) {");
+                ctxt.generateJavaSource("        " + varMapper + 
".setVariable(\"" + strVar + "\", null);");
+                ctxt.generateJavaSource("    }");
+            }
+            ctxt.generateJavaSource("    " + jspCtxt + ".setAttribute(\"" + 
strVar + "\"," + resultName + "," +
+                    iScope + ");");
             ctxt.generateJavaSource("} else {");
+            ctxt.generateJavaSource("    if(" + varMapper + 
".resolveVariable(\"" + strVar + "\") != null) {");
+            ctxt.generateJavaSource("        " + varMapper + ".setVariable(\"" 
+ strVar + "\", null);");
+            ctxt.generateJavaSource("    }");
             if (hasScope) {
                 ctxt.generateJavaSource("    " + jspCtxt + 
".removeAttribute(\"" + strVar + "\"," + iScope + ");");
             } else {
diff --git a/test/org/apache/jasper/tagplugins/jstl/core/TestSet.java 
b/test/org/apache/jasper/tagplugins/jstl/core/TestSet.java
index 07e57e7cf7..000e3e255c 100644
--- a/test/org/apache/jasper/tagplugins/jstl/core/TestSet.java
+++ b/test/org/apache/jasper/tagplugins/jstl/core/TestSet.java
@@ -51,4 +51,42 @@ public class TestSet extends AbstractTestTag {
         String body = res.toString();
         Assert.assertTrue(body.contains("OK - 42"));
     }
+
+
+    /*
+     * A deferred value expression on a c:set with var is stored as a
+     * mapping in the variable mapper of the EL context, not as an
+     * attribute, so later expressions evaluate it lazily. Only the page
+     * scope can hold such a mapping.
+     */
+    @Test
+    public void testDeferredValue() throws Exception {
+        ByteChunk res = new ByteChunk();
+
+        int rc = getUrl("http://localhost:"; + getPort() +
+                "/test/jstl/set-deferred.jsp", res, null);
+
+        Assert.assertEquals(HttpServletResponse.SC_OK, rc);
+
+        String body = res.toString();
+        // The deferred value evaluates lazily when it is referenced, and is
+        // not kept as an attribute
+        Assert.assertTrue(body, body.contains("DEFERRED=[2]"));
+        Assert.assertTrue(body, body.contains("ATTR=[null]"));
+        // An explicit page scope behaves the same way
+        Assert.assertTrue(body, body.contains("EXPLICIT=[2]"));
+        // Setting the variable again replaces the mapping
+        Assert.assertTrue(body, body.contains("REPLACE=[3]"));
+        // A later plain value replaces the deferred one
+        Assert.assertTrue(body, body.contains("FIRST=[lazy]"));
+        Assert.assertTrue(body, body.contains("SECOND=[plain]"));
+        // A null value removes the variable, deferred or not
+        Assert.assertTrue(body, body.contains("THIRD=[]"));
+        // A deferred value in another scope fails
+        Assert.assertTrue(body, body.contains("ERR=[threw]"));
+        Assert.assertTrue(body,
+                body.contains("ERRMSG=[Invalid scope for ValueExpression in 
&lt;set&gt;. "
+                        + "\"page\" scope is required but was \"request\"]"));
+        Assert.assertTrue(body, body.contains("ERR2=[threw]"));
+    }
 }
diff --git a/test/webapp/jstl/set-deferred.jsp 
b/test/webapp/jstl/set-deferred.jsp
new file mode 100644
index 0000000000..fc969a9cc5
--- /dev/null
+++ b/test/webapp/jstl/set-deferred.jsp
@@ -0,0 +1,39 @@
+<%--
+ 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.
+--%>
+<%@ page session="false" contentType="text/html; charset=UTF-8"
+         pageEncoding="UTF-8" %>
+<%@ taglib uri="http://java.sun.com/jsp/jstl/core"; prefix="c" %>
+<c:set var="d" value="#{1+1}" />
+<p>DEFERRED=[${d}]</p>
+<% out.print("ATTR=[" + pageContext.getAttribute("d") + "]"); %>
+<c:set var="d5" value="#{2}" scope="page" />
+<p>EXPLICIT=[${d5}]</p>
+<c:set var="d6" value="#{1}" />
+<c:set var="d6" value="#{3}" />
+<p>REPLACE=[${d6}]</p>
+<c:set var="d2" value="#{'lazy'}" />
+<p>FIRST=[${d2}]</p>
+<c:set var="d2" value="plain" />
+<p>SECOND=[${d2}]</p>
+<c:set var="d3" value="#{'gone'}" />
+<c:set var="d3" value="${null}" />
+<p>THIRD=[${d3}]</p>
+<c:catch var="err"><c:set var="d4" value="#{1+1}" scope="request" /></c:catch>
+<p>ERR=[${err ne null ? 'threw' : 'silent'}]</p>
+<p>ERRMSG=[${err.message}]</p>
+<c:catch var="err2"><c:set var="d7" value="#{1+1}" scope="session" /></c:catch>
+<p>ERR2=[${err2 ne null ? 'threw' : 'silent'}]</p>
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 9cd0a365f8..6291c8d78d 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -459,6 +459,17 @@
         inline, including its surrounding whitespace, and did not escape
         the body content. (remm)
       </fix>
+      <fix>
+        The tag plugin for <code>c:set</code> now handles a deferred value
+        expression (<code>#{...}</code>) with a <code>var</code> attribute
+        like the standard tag handler does. Such a value is passed to the
+        tag as a value expression object, and it is now stored as a mapping
+        in the variable mapper of the EL context, where it evaluates
+        lazily, rather than being stored as an attribute holding the raw
+        expression object. As in the standard handler, a non-page scope is
+        rejected for a deferred value, and mappings that would shadow a
+        plain attribute are removed. (remm)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="WebSocket">


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

Reply via email to