Fix static inner class resolution in Painless (#67027)

When removing the "lexer hack" to remove type context from the lexer, static inner class resolution 
wasn't properly accounted for. This change adds code to handle static inner class resolution.
This commit is contained in:
Jack Conradson 2021-01-05 11:05:24 -08:00
parent 67f43ffc5f
commit 8e294d53bc
2 changed files with 224 additions and 199 deletions

View File

@ -2461,220 +2461,238 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor<SemanticSc
String canonicalTypeName =
semanticScope.getDecoration(userPrefixNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "." + index;
Class<?> type = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
Class<?> staticType = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName);
if (type == null) {
if (staticType == null) {
semanticScope.putDecoration(userDotNode, new PartialCanonicalTypeName(canonicalTypeName));
} else {
if (write) {
throw userDotNode.createError(new IllegalArgumentException("invalid assignment: " +
"cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "]"));
"cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "]"));
}
semanticScope.putDecoration(userDotNode, new StaticType(type));
semanticScope.putDecoration(userDotNode, new StaticType(staticType));
}
} else {
Class<?> valueType = null;
Class<?> staticType = null;
if (prefixValueType != null && prefixValueType.getValueType().isArray()) {
if ("length".equals(index)) {
if (write) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value write to read-only field [length] for an array."));
}
valueType = int.class;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Field [" + index + "] does not exist for type [" + prefixValueType.getValueCanonicalTypeName() + "]."));
}
} else if (prefixValueType != null && prefixValueType.getValueType() == def.class) {
TargetType targetType = userDotNode.isNullSafe() ? null : semanticScope.getDecoration(userDotNode, TargetType.class);
// TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class ||
semanticScope.getCondition(userDotNode, Explicit.class) ? def.class : targetType.getTargetType();
if (write) {
semanticScope.setCondition(userDotNode, DefOptimized.class);
}
} else {
Class<?> prefixType;
String prefixCanonicalTypeName;
boolean isStatic;
if (prefixValueType != null) {
prefixType = prefixValueType.getValueType();
prefixCanonicalTypeName = prefixValueType.getValueCanonicalTypeName();
isStatic = false;
} else if (prefixStaticType != null) {
prefixType = prefixStaticType.getStaticType();
prefixCanonicalTypeName = prefixStaticType.getStaticCanonicalTypeName();
isStatic = true;
} else {
throw userDotNode.createError(new IllegalStateException("value required: instead found no value"));
}
PainlessField field = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessField(prefixType, isStatic, index);
if (field == null) {
PainlessMethod getter;
PainlessMethod setter;
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"get" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
if (getter == null) {
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"is" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
}
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"set" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
if (getter != null || setter != null) {
if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal get shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && (setter.returnType != void.class || setter.typeParameters.size() != 1)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal set shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null && setter.typeParameters.get(0) != getter.returnType) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(0) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, Shortcut.class);
} else if (isStatic == false) {
if (Map.class.isAssignableFrom(prefixValueType.getValueType())) {
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "put", 2);
if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map get shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && setter.typeParameters.size() != 2) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map set shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) ||
getter.returnType.equals(setter.typeParameters.get(1)) == false)) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, MapShortcut.class);
}
if (List.class.isAssignableFrom(prefixType)) {
try {
scriptScope.putDecoration(userDotNode, new StandardConstant(Integer.parseInt(index)));
} catch (NumberFormatException nfe) {
throw userDotNode.createError(new IllegalArgumentException("invalid list index [" + index + "]", nfe));
}
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "set", 2);
if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 ||
getter.typeParameters.get(0) != int.class)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list get shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && (setter.typeParameters.size() != 2 || setter.typeParameters.get(0) != int.class)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list set shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0))
|| !getter.returnType.equals(setter.typeParameters.get(1)))) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, ListShortcut.class);
}
}
if (valueType == null) {
if (prefixValueType != null) {
throw userDotNode.createError(new IllegalArgumentException(
"field [" + prefixValueType.getValueCanonicalTypeName() + ", " + index + "] not found"));
} else {
throw userDotNode.createError(new IllegalArgumentException(
"field [" + prefixStaticType.getStaticCanonicalTypeName() + ", " + index + "] not found"));
}
}
} else {
if (write && Modifier.isFinal(field.javaField.getModifiers())) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value to read-only field [" + field.javaField.getName() + "]"));
}
semanticScope.putDecoration(userDotNode, new StandardPainlessField(field));
valueType = field.typeParameter;
}
if (prefixStaticType != null) {
String staticCanonicalTypeName = prefixStaticType.getStaticCanonicalTypeName() + "." + userDotNode.getIndex();
staticType = scriptScope.getPainlessLookup().canonicalTypeNameToType(staticCanonicalTypeName);
}
semanticScope.putDecoration(userDotNode, new ValueType(valueType));
if (userDotNode.isNullSafe()) {
if (staticType != null) {
if (write) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value to a null safe operation [?.]"));
throw userDotNode.createError(new IllegalArgumentException("invalid assignment: " +
"cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "]"));
}
if (valueType.isPrimitive()) {
throw new IllegalArgumentException("Result of null safe operator must be nullable");
semanticScope.putDecoration(userDotNode, new StaticType(staticType));
} else {
Class<?> valueType = null;
if (prefixValueType != null && prefixValueType.getValueType().isArray()) {
if ("length".equals(index)) {
if (write) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value write to read-only field [length] for an array."));
}
valueType = int.class;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Field [" + index + "] does not exist for type [" + prefixValueType.getValueCanonicalTypeName() + "]."));
}
} else if (prefixValueType != null && prefixValueType.getValueType() == def.class) {
TargetType targetType = userDotNode.isNullSafe() ? null : semanticScope.getDecoration(userDotNode, TargetType.class);
// TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed
valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class ||
semanticScope.getCondition(userDotNode, Explicit.class) ? def.class : targetType.getTargetType();
if (write) {
semanticScope.setCondition(userDotNode, DefOptimized.class);
}
} else {
Class<?> prefixType;
String prefixCanonicalTypeName;
boolean isStatic;
if (prefixValueType != null) {
prefixType = prefixValueType.getValueType();
prefixCanonicalTypeName = prefixValueType.getValueCanonicalTypeName();
isStatic = false;
} else if (prefixStaticType != null) {
prefixType = prefixStaticType.getStaticType();
prefixCanonicalTypeName = prefixStaticType.getStaticCanonicalTypeName();
isStatic = true;
} else {
throw userDotNode.createError(new IllegalStateException("value required: instead found no value"));
}
PainlessField field =
semanticScope.getScriptScope().getPainlessLookup().lookupPainlessField(prefixType, isStatic, index);
if (field == null) {
PainlessMethod getter;
PainlessMethod setter;
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"get" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
if (getter == null) {
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"is" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
}
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, isStatic,
"set" + Character.toUpperCase(index.charAt(0)) + index.substring(1), 0);
if (getter != null || setter != null) {
if (getter != null && (getter.returnType == void.class || !getter.typeParameters.isEmpty())) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal get shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && (setter.returnType != void.class || setter.typeParameters.size() != 1)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal set shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null && setter.typeParameters.get(0) != getter.returnType) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(0) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal shortcut on field [" + index + "] for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, Shortcut.class);
} else if (isStatic == false) {
if (Map.class.isAssignableFrom(prefixValueType.getValueType())) {
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "put", 2);
if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map get shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && setter.typeParameters.size() != 2) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map set shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null &&
(!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) ||
getter.returnType.equals(setter.typeParameters.get(1)) == false)) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal map shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, MapShortcut.class);
}
if (List.class.isAssignableFrom(prefixType)) {
try {
scriptScope.putDecoration(userDotNode, new StandardConstant(Integer.parseInt(index)));
} catch (NumberFormatException nfe) {
throw userDotNode.createError(new IllegalArgumentException("invalid list index [" + index + "]", nfe));
}
getter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "get", 1);
setter = scriptScope.getPainlessLookup().lookupPainlessMethod(prefixType, false, "set", 2);
if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 ||
getter.typeParameters.get(0) != int.class)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list get shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (setter != null && (setter.typeParameters.size() != 2 || setter.typeParameters.get(0) != int.class)) {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list set shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0))
|| !getter.returnType.equals(setter.typeParameters.get(1)))) {
throw userDotNode.createError(new IllegalArgumentException("Shortcut argument types must match."));
}
if ((read == false || getter != null) && (write == false || setter != null)) {
valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType;
} else {
throw userDotNode.createError(new IllegalArgumentException(
"Illegal list shortcut for type [" + prefixCanonicalTypeName + "]."));
}
if (getter != null) {
semanticScope.putDecoration(userDotNode, new GetterPainlessMethod(getter));
}
if (setter != null) {
semanticScope.putDecoration(userDotNode, new SetterPainlessMethod(setter));
}
semanticScope.setCondition(userDotNode, ListShortcut.class);
}
}
if (valueType == null) {
if (prefixValueType != null) {
throw userDotNode.createError(new IllegalArgumentException(
"field [" + prefixValueType.getValueCanonicalTypeName() + ", " + index + "] not found"));
} else {
throw userDotNode.createError(new IllegalArgumentException(
"field [" + prefixStaticType.getStaticCanonicalTypeName() + ", " + index + "] not found"));
}
}
} else {
if (write && Modifier.isFinal(field.javaField.getModifiers())) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value to read-only field [" + field.javaField.getName() + "]"));
}
semanticScope.putDecoration(userDotNode, new StandardPainlessField(field));
valueType = field.typeParameter;
}
}
semanticScope.putDecoration(userDotNode, new ValueType(valueType));
if (userDotNode.isNullSafe()) {
if (write) {
throw userDotNode.createError(new IllegalArgumentException(
"invalid assignment: cannot assign a value to a null safe operation [?.]"));
}
if (valueType.isPrimitive()) {
throw new IllegalArgumentException("Result of null safe operator must be nullable");
}
}
}
}

View File

@ -19,6 +19,8 @@
package org.elasticsearch.painless;
import java.text.MessageFormat.Field;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
@ -166,4 +168,9 @@ public class BasicAPITests extends ScriptTestCase {
).matches()
);
}
public void testStaticInnerClassResolution() {
assertEquals(Field.ARGUMENT, exec("MessageFormat.Field.ARGUMENT"));
assertEquals(Normalizer.Form.NFD, exec("Normalizer.Form.NFD"));
}
}