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

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


The following commit(s) were added to refs/heads/11.0.x by this push:
     new 296e1a6218 Review "Resolve imported identifiers in 
AstIdentifier.getType()"
296e1a6218 is described below

commit 296e1a62188dfffeacc2de53f9dc75d53d3422ca
Author: Mark Thomas <[email protected]>
AuthorDate: Thu Nov 5 15:43:04 2026 +0000

    Review "Resolve imported identifiers in AstIdentifier.getType()"
    
    Need to resolve identifiers for all lookups
    
    Co-authored by: Claude / Sonnet 5.5
---
 java/org/apache/el/parser/AstIdentifier.java     |  87 ++++++++++++-----
 test/org/apache/el/parser/TestAstIdentifier.java | 115 +++++++++++++++++++++++
 2 files changed, 179 insertions(+), 23 deletions(-)

diff --git a/java/org/apache/el/parser/AstIdentifier.java 
b/java/org/apache/el/parser/AstIdentifier.java
index 35b26b9014..e5ae96f897 100644
--- a/java/org/apache/el/parser/AstIdentifier.java
+++ b/java/org/apache/el/parser/AstIdentifier.java
@@ -17,6 +17,8 @@
 /* Generated By:JJTree: Do not edit this line. AstIdentifier.java */
 package org.apache.el.parser;
 
+import java.lang.reflect.Field;
+
 import jakarta.el.ELClass;
 import jakarta.el.ELException;
 import jakarta.el.MethodExpression;
@@ -24,6 +26,7 @@ import jakarta.el.MethodInfo;
 import jakarta.el.MethodNotFoundException;
 import jakarta.el.MethodReference;
 import jakarta.el.PropertyNotFoundException;
+import jakarta.el.PropertyNotWritableException;
 import jakarta.el.ValueExpression;
 import jakarta.el.ValueReference;
 import jakarta.el.VariableMapper;
@@ -39,6 +42,12 @@ public final class AstIdentifier extends SimpleNode {
 
     @Override
     public Class<?> getType(EvaluationContext ctx) throws ELException {
+        // Lambda parameters
+        if (ctx.isLambdaArgument(this.image)) {
+            Object argument = ctx.getLambdaArgument(this.image);
+            return (argument != null) ? argument.getClass() : null;
+        }
+
         VariableMapper varMapper = ctx.getVariableMapper();
         if (varMapper != null) {
             ValueExpression expr = varMapper.resolveVariable(this.image);
@@ -53,19 +62,13 @@ public final class AstIdentifier extends SimpleNode {
         }
 
         // Import
-        Object imported = ctx.getImportHandler().resolveClass(this.image);
-        if (imported != null) {
+        Object imported = resolveImport(ctx);
+        if (imported instanceof ELClass) {
             // getValue() returns an ELClass for an imported class
             return ELClass.class;
-        }
-        imported = ctx.getImportHandler().resolveStatic(this.image);
-        if (imported != null) {
+        } else if (imported instanceof Field field) {
             // getValue() returns the value of a statically imported field
-            try {
-                return ((Class<?>) imported).getField(this.image).getType();
-            } catch (IllegalArgumentException | NoSuchFieldException | 
SecurityException e) {
-                throw new ELException(e);
-            }
+            return field.getType();
         }
 
         throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
@@ -114,15 +117,13 @@ public final class AstIdentifier extends SimpleNode {
         }
 
         // Import
-        result = ctx.getImportHandler().resolveClass(this.image);
-        if (result != null) {
-            return new ELClass((Class<?>) result);
-        }
-        result = ctx.getImportHandler().resolveStatic(this.image);
-        if (result != null) {
+        Object imported = resolveImport(ctx);
+        if (imported instanceof ELClass) {
+            return imported;
+        } else if (imported instanceof Field field) {
             try {
-                return ((Class<?>) result).getField(this.image).get(null);
-            } catch (IllegalArgumentException | IllegalAccessException | 
NoSuchFieldException | SecurityException e) {
+                return field.get(null);
+            } catch (IllegalArgumentException | IllegalAccessException e) {
                 throw new ELException(e);
             }
         }
@@ -141,10 +142,16 @@ public final class AstIdentifier extends SimpleNode {
         }
         ctx.setPropertyResolved(false);
         boolean result = ctx.getELResolver().isReadOnly(ctx, null, this.image);
-        if (!ctx.isPropertyResolved()) {
-            throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
+        if (ctx.isPropertyResolved()) {
+            return result;
+        }
+
+        // Imported classes and statically imported fields can not be set
+        if (resolveImport(ctx) != null) {
+            return true;
         }
-        return result;
+
+        throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
     }
 
     @Override
@@ -159,9 +166,43 @@ public final class AstIdentifier extends SimpleNode {
         }
         ctx.setPropertyResolved(false);
         ctx.getELResolver().setValue(ctx, null, this.image, value);
-        if (!ctx.isPropertyResolved()) {
-            throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
+        if (ctx.isPropertyResolved()) {
+            return;
+        }
+
+        // Imported classes and statically imported fields can not be set
+        if (resolveImport(ctx) != null) {
+            throw new 
PropertyNotWritableException(MessageFactory.get("error.syntax.set"));
+        }
+
+        throw new 
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null", 
this.image));
+    }
+
+
+    /**
+     * Resolve this identifier using the import handler.
+     *
+     * @param ctx The evaluation context
+     *
+     * @return An {@link ELClass} if the identifier is an imported class, the 
{@link Field} if the identifier is a
+     *             statically imported field or {@code null} if the identifier 
cannot be resolved by the import handler
+     *
+     * @throws ELException If the statically imported field cannot be found
+     */
+    private Object resolveImport(EvaluationContext ctx) throws ELException {
+        Object result = ctx.getImportHandler().resolveClass(this.image);
+        if (result != null) {
+            return new ELClass((Class<?>) result);
+        }
+        result = ctx.getImportHandler().resolveStatic(this.image);
+        if (result != null) {
+            try {
+                return ((Class<?>) result).getField(this.image);
+            } catch (NoSuchFieldException | SecurityException e) {
+                throw new ELException(e);
+            }
         }
+        return null;
     }
 
     @Override
diff --git a/test/org/apache/el/parser/TestAstIdentifier.java 
b/test/org/apache/el/parser/TestAstIdentifier.java
index b981dfc572..e1c47d1bd4 100644
--- a/test/org/apache/el/parser/TestAstIdentifier.java
+++ b/test/org/apache/el/parser/TestAstIdentifier.java
@@ -16,6 +16,9 @@
  */
 package org.apache.el.parser;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import jakarta.el.ELClass;
 import jakarta.el.ELContext;
 import jakarta.el.ELException;
@@ -24,12 +27,15 @@ import jakarta.el.ELProcessor;
 import jakarta.el.ELResolver;
 import jakarta.el.ExpressionFactory;
 import jakarta.el.PropertyNotFoundException;
+import jakarta.el.PropertyNotWritableException;
 import jakarta.el.ValueExpression;
 
 import org.junit.Assert;
 import org.junit.Assume;
 import org.junit.Test;
 
+import org.apache.el.lang.EvaluationContext;
+import org.apache.el.lang.ExpressionBuilder;
 import org.apache.jasper.el.ELContextImpl;
 import org.apache.tomcat.util.compat.JreCompat;
 
@@ -132,6 +138,115 @@ public class TestAstIdentifier {
     }
 
 
+    @Test
+    public void testImportIsReadOnlyClass() {
+        // An imported class can not be set so it must be reported as 
read-only, just as getValue() and getType()
+        // resolve it
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${Integer}", Object.class);
+        Assert.assertTrue(ve.isReadOnly(context));
+    }
+
+
+    @Test
+    public void testImportIsReadOnlyStaticField() {
+        ELProcessor processor = new ELProcessor();
+        
processor.getELManager().getELContext().getImportHandler().importStatic("java.lang.Integer.MAX_VALUE");
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${MAX_VALUE}", Object.class);
+        Assert.assertTrue(ve.isReadOnly(context));
+    }
+
+
+    @Test(expected = PropertyNotWritableException.class)
+    public void testImportSetValueClass() {
+        // ELProcessor's resolvers would create a bean with this name so use a 
context without them
+        ELContext context = new ELContextImpl();
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ValueExpression ve = factory.createValueExpression(context,
+                "${Integer}", Object.class);
+        ve.setValue(context, "x");
+    }
+
+
+    @Test(expected = PropertyNotWritableException.class)
+    public void testImportSetValueStaticField() {
+        // ELProcessor's resolvers would create a bean with this name so use a 
context without them
+        ELContext context = new ELContextImpl();
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        context.getImportHandler().importStatic("java.lang.Integer.MAX_VALUE");
+        ValueExpression ve = factory.createValueExpression(context,
+                "${MAX_VALUE}", Object.class);
+        ve.setValue(context, Integer.valueOf(1));
+    }
+
+
+    @Test(expected = PropertyNotFoundException.class)
+    public void testUnresolvedIsReadOnly() {
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        ValueExpression ve = 
processor.getELManager().getExpressionFactory().createValueExpression(context,
+                "${unresolvedName}", Object.class);
+        ve.isReadOnly(context);
+    }
+
+
+    @Test(expected = PropertyNotFoundException.class)
+    public void testUnresolvedSetValue() {
+        // ELProcessor's resolvers would create a bean with this name so use a 
context without them
+        ELContext context = new ELContextImpl();
+        ExpressionFactory factory = ExpressionFactory.newInstance();
+        ValueExpression ve = factory.createValueExpression(context,
+                "${unresolvedName}", Object.class);
+        ve.setValue(context, "x");
+    }
+
+
+    @Test
+    public void testLambdaArgumentGetType() {
+        // A lambda argument takes precedence over an imported class of the 
same name for getType(), just as it does
+        // for getValue()
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        EvaluationContext ctx = new EvaluationContext(context, null, null);
+        Node node = ExpressionBuilder.createNode("${Integer}");
+
+        Map<String,Object> arguments = new HashMap<>();
+        arguments.put("Integer", "text");
+        ctx.enterLambdaScope(arguments);
+        try {
+            Assert.assertEquals("text", node.getValue(ctx));
+            Assert.assertEquals(String.class, node.getType(ctx));
+        } finally {
+            ctx.exitLambdaScope();
+        }
+        // Outside the lambda the import applies
+        Assert.assertEquals(ELClass.class, node.getType(ctx));
+    }
+
+
+    @Test
+    public void testLambdaArgumentGetTypeNull() {
+        ELProcessor processor = new ELProcessor();
+        ELContext context = processor.getELManager().getELContext();
+        EvaluationContext ctx = new EvaluationContext(context, null, null);
+        Node node = ExpressionBuilder.createNode("${Integer}");
+
+        Map<String,Object> arguments = new HashMap<>();
+        arguments.put("Integer", null);
+        ctx.enterLambdaScope(arguments);
+        try {
+            Assert.assertNull(node.getValue(ctx));
+            Assert.assertNull(node.getType(ctx));
+        } finally {
+            ctx.exitLambdaScope();
+        }
+    }
+
+
     @Test
     public void testUnresolvedGetType() {
         // A name that getValue() cannot resolve must continue to report a


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

Reply via email to