From 8e294d53bca8708cc9b7cc257bcb37229fbce4c8 Mon Sep 17 00:00:00 2001 From: Jack Conradson Date: Tue, 5 Jan 2021 11:05:24 -0800 Subject: [PATCH] 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. --- .../phase/DefaultSemanticAnalysisPhase.java | 416 +++++++++--------- .../elasticsearch/painless/BasicAPITests.java | 7 + 2 files changed, 224 insertions(+), 199 deletions(-) diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java index 5b1e67b4867..ee863150050 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java @@ -2461,220 +2461,238 @@ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor 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"); + } } } } diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java index 4209402bcb2..c62a31a242b 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/BasicAPITests.java @@ -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")); + } }