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 1e88dd946dfafbbf633f9e332ce4bce007e3fe3f 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 c6e2cd3442..e4af208be5 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 " + + "<set>. \\\"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 <set>. " + + "\"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 7c6798263a..bc4a623f0c 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]
