This is an automated email from the ASF dual-hosted git repository. markt-asf pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 6cedf63c1cac2083646c518fe2b319921be50024 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 | 114 +++++++++++++++++++++++ 2 files changed, 178 insertions(+), 23 deletions(-) diff --git a/java/org/apache/el/parser/AstIdentifier.java b/java/org/apache/el/parser/AstIdentifier.java index f6fffb9e31..30bdfcffc3 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.ELResolver; @@ -25,6 +27,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; @@ -40,6 +43,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); @@ -54,19 +63,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)); @@ -113,15 +116,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); } } @@ -140,10 +141,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 @@ -158,9 +165,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 6e40fa923d..5549459fac 100644 --- a/test/org/apache/el/parser/TestAstIdentifier.java +++ b/test/org/apache/el/parser/TestAstIdentifier.java @@ -17,7 +17,9 @@ package org.apache.el.parser; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import jakarta.el.ELClass; import jakarta.el.ELContext; @@ -27,12 +29,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; public class TestAstIdentifier { @@ -179,6 +184,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]
