This is an automated email from the ASF dual-hosted git repository.
markt-asf pushed a commit to branch 9.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/9.0.x by this push:
new 78fc08378c Review "Resolve imported identifiers in
AstIdentifier.getType()"
78fc08378c is described below
commit 78fc08378c9f7484843c9b7a7eb09684d4ad18d8
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 2a64dd786b..8370a21ef7 100644
--- a/java/org/apache/el/parser/AstIdentifier.java
+++ b/java/org/apache/el/parser/AstIdentifier.java
@@ -17,12 +17,15 @@
/* Generated By:JJTree: Do not edit this line. AstIdentifier.java */
package org.apache.el.parser;
+import java.lang.reflect.Field;
+
import javax.el.ELClass;
import javax.el.ELException;
import javax.el.MethodExpression;
import javax.el.MethodInfo;
import javax.el.MethodNotFoundException;
import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
import javax.el.ValueExpression;
import javax.el.ValueReference;
import javax.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) {
// 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) imported).getType();
}
throw new
PropertyNotFoundException(MessageFactory.get("error.resolver.unhandled.null",
this.image));
@@ -116,15 +119,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) {
try {
- return ((Class<?>) result).getField(this.image).get(null);
- } catch (IllegalArgumentException | IllegalAccessException |
NoSuchFieldException | SecurityException e) {
+ return ((Field) imported).get(null);
+ } catch (IllegalArgumentException | IllegalAccessException e) {
throw new ELException(e);
}
}
@@ -144,10 +145,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));
}
@@ -163,9 +170,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;
}
diff --git a/test/org/apache/el/parser/TestAstIdentifier.java
b/test/org/apache/el/parser/TestAstIdentifier.java
index cf081e5213..18f6a3f9e5 100644
--- a/test/org/apache/el/parser/TestAstIdentifier.java
+++ b/test/org/apache/el/parser/TestAstIdentifier.java
@@ -17,8 +17,11 @@
package org.apache.el.parser;
import java.beans.FeatureDescriptor;
+import java.util.HashMap;
import java.util.Iterator;
+import java.util.Map;
+import javax.el.CompositeELResolver;
import javax.el.ELClass;
import javax.el.ELContext;
import javax.el.ELException;
@@ -27,12 +30,15 @@ import javax.el.ELProcessor;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.PropertyNotFoundException;
+import javax.el.PropertyNotWritableException;
import javax.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;
@@ -140,6 +146,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(new CompositeELResolver());
+ 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(new CompositeELResolver());
+ 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(new CompositeELResolver());
+ 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]