/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. */ /* * Licensed to Elasticsearch under one or more contributor * license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright * ownership. Elasticsearch licenses this file to you under * the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ /* * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ package org.opensearch.painless.phase; import org.opensearch.painless.AnalyzerCaster; import org.opensearch.painless.CompilerSettings; import org.opensearch.painless.FunctionRef; import org.opensearch.painless.Location; import org.opensearch.painless.Operation; import org.opensearch.painless.lookup.PainlessCast; import org.opensearch.painless.lookup.PainlessClassBinding; import org.opensearch.painless.lookup.PainlessConstructor; import org.opensearch.painless.lookup.PainlessField; import org.opensearch.painless.lookup.PainlessInstanceBinding; import org.opensearch.painless.lookup.PainlessLookupUtility; import org.opensearch.painless.lookup.PainlessMethod; import org.opensearch.painless.lookup.def; import org.opensearch.painless.node.AExpression; import org.opensearch.painless.node.ANode; import org.opensearch.painless.node.AStatement; import org.opensearch.painless.node.EAssignment; import org.opensearch.painless.node.EBinary; import org.opensearch.painless.node.EBooleanComp; import org.opensearch.painless.node.EBooleanConstant; import org.opensearch.painless.node.EBrace; import org.opensearch.painless.node.ECall; import org.opensearch.painless.node.ECallLocal; import org.opensearch.painless.node.EComp; import org.opensearch.painless.node.EConditional; import org.opensearch.painless.node.EDecimal; import org.opensearch.painless.node.EDot; import org.opensearch.painless.node.EElvis; import org.opensearch.painless.node.EExplicit; import org.opensearch.painless.node.EFunctionRef; import org.opensearch.painless.node.EInstanceof; import org.opensearch.painless.node.ELambda; import org.opensearch.painless.node.EListInit; import org.opensearch.painless.node.EMapInit; import org.opensearch.painless.node.ENewArray; import org.opensearch.painless.node.ENewArrayFunctionRef; import org.opensearch.painless.node.ENewObj; import org.opensearch.painless.node.ENull; import org.opensearch.painless.node.ENumeric; import org.opensearch.painless.node.ERegex; import org.opensearch.painless.node.EString; import org.opensearch.painless.node.ESymbol; import org.opensearch.painless.node.EUnary; import org.opensearch.painless.node.SBlock; import org.opensearch.painless.node.SBreak; import org.opensearch.painless.node.SCatch; import org.opensearch.painless.node.SClass; import org.opensearch.painless.node.SContinue; import org.opensearch.painless.node.SDeclBlock; import org.opensearch.painless.node.SDeclaration; import org.opensearch.painless.node.SDo; import org.opensearch.painless.node.SEach; import org.opensearch.painless.node.SExpression; import org.opensearch.painless.node.SFor; import org.opensearch.painless.node.SFunction; import org.opensearch.painless.node.SIf; import org.opensearch.painless.node.SIfElse; import org.opensearch.painless.node.SReturn; import org.opensearch.painless.node.SThrow; import org.opensearch.painless.node.STry; import org.opensearch.painless.node.SWhile; import org.opensearch.painless.spi.annotation.NonDeterministicAnnotation; import org.opensearch.painless.symbol.Decorations; import org.opensearch.painless.symbol.Decorations.AllEscape; import org.opensearch.painless.symbol.Decorations.AnyBreak; import org.opensearch.painless.symbol.Decorations.AnyContinue; import org.opensearch.painless.symbol.Decorations.BeginLoop; import org.opensearch.painless.symbol.Decorations.BinaryType; import org.opensearch.painless.symbol.Decorations.CapturesDecoration; import org.opensearch.painless.symbol.Decorations.ComparisonType; import org.opensearch.painless.symbol.Decorations.CompoundType; import org.opensearch.painless.symbol.Decorations.ContinuousLoop; import org.opensearch.painless.symbol.Decorations.DefOptimized; import org.opensearch.painless.symbol.Decorations.DowncastPainlessCast; import org.opensearch.painless.symbol.Decorations.EncodingDecoration; import org.opensearch.painless.symbol.Decorations.Explicit; import org.opensearch.painless.symbol.Decorations.ExpressionPainlessCast; import org.opensearch.painless.symbol.Decorations.GetterPainlessMethod; import org.opensearch.painless.symbol.Decorations.InLoop; import org.opensearch.painless.symbol.Decorations.InstanceType; import org.opensearch.painless.symbol.Decorations.Internal; import org.opensearch.painless.symbol.Decorations.IterablePainlessMethod; import org.opensearch.painless.symbol.Decorations.LastLoop; import org.opensearch.painless.symbol.Decorations.LastSource; import org.opensearch.painless.symbol.Decorations.ListShortcut; import org.opensearch.painless.symbol.Decorations.LoopEscape; import org.opensearch.painless.symbol.Decorations.MapShortcut; import org.opensearch.painless.symbol.Decorations.MethodEscape; import org.opensearch.painless.symbol.Decorations.MethodNameDecoration; import org.opensearch.painless.symbol.Decorations.Negate; import org.opensearch.painless.symbol.Decorations.ParameterNames; import org.opensearch.painless.symbol.Decorations.PartialCanonicalTypeName; import org.opensearch.painless.symbol.Decorations.Read; import org.opensearch.painless.symbol.Decorations.ReferenceDecoration; import org.opensearch.painless.symbol.Decorations.ReturnType; import org.opensearch.painless.symbol.Decorations.SemanticVariable; import org.opensearch.painless.symbol.Decorations.SetterPainlessMethod; import org.opensearch.painless.symbol.Decorations.ShiftType; import org.opensearch.painless.symbol.Decorations.Shortcut; import org.opensearch.painless.symbol.Decorations.StandardConstant; import org.opensearch.painless.symbol.Decorations.StandardLocalFunction; import org.opensearch.painless.symbol.Decorations.StandardPainlessClassBinding; import org.opensearch.painless.symbol.Decorations.StandardPainlessConstructor; import org.opensearch.painless.symbol.Decorations.StandardPainlessField; import org.opensearch.painless.symbol.Decorations.StandardPainlessInstanceBinding; import org.opensearch.painless.symbol.Decorations.StandardPainlessMethod; import org.opensearch.painless.symbol.Decorations.StaticType; import org.opensearch.painless.symbol.Decorations.TargetType; import org.opensearch.painless.symbol.Decorations.TypeParameters; import org.opensearch.painless.symbol.Decorations.UnaryType; import org.opensearch.painless.symbol.Decorations.UpcastPainlessCast; import org.opensearch.painless.symbol.Decorations.ValueType; import org.opensearch.painless.symbol.Decorations.Write; import org.opensearch.painless.symbol.FunctionTable; import org.opensearch.painless.symbol.FunctionTable.LocalFunction; import org.opensearch.painless.symbol.ScriptScope; import org.opensearch.painless.symbol.SemanticScope; import org.opensearch.painless.symbol.SemanticScope.FunctionScope; import org.opensearch.painless.symbol.SemanticScope.LambdaScope; import org.opensearch.painless.symbol.SemanticScope.Variable; import java.lang.reflect.Modifier; import java.time.ZonedDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import static org.opensearch.painless.lookup.PainlessLookupUtility.typeToCanonicalTypeName; import static org.opensearch.painless.symbol.SemanticScope.newFunctionScope; /** * Semantically validates a user tree visiting all user tree nodes to check for * valid control flow, valid types, valid variable resolution, valid method resolution, * valid field resolution, and other specialized validation. */ public class DefaultSemanticAnalysisPhase extends UserTreeBaseVisitor { /** * Decorates a user expression node with a PainlessCast. */ public void decorateWithCast(AExpression userExpressionNode, SemanticScope semanticScope) { Location location = userExpressionNode.getLocation(); Class valueType = semanticScope.getDecoration(userExpressionNode, ValueType.class).getValueType(); Class targetType = semanticScope.getDecoration(userExpressionNode, TargetType.class).getTargetType(); boolean isExplicitCast = semanticScope.getCondition(userExpressionNode, Explicit.class); boolean isInternalCast = semanticScope.getCondition(userExpressionNode, Internal.class); PainlessCast painlessCast = AnalyzerCaster.getLegalCast(location, valueType, targetType, isExplicitCast, isInternalCast); if (painlessCast != null) { semanticScope.putDecoration(userExpressionNode, new ExpressionPainlessCast(painlessCast)); } } /** * Shortcut to visit a user tree node with a null check. */ public void visit(ANode userNode, SemanticScope semanticScope) { if (userNode != null) { userNode.visit(this, semanticScope); } } /** * Shortcut to visit a user expression node with additional checks common to most expression nodes. These * additional checks include looking for an escaped partial canonical type, an unexpected static type, and an * unexpected value type. */ public void checkedVisit(AExpression userExpressionNode, SemanticScope semanticScope) { if (userExpressionNode != null) { userExpressionNode.visit(this, semanticScope); if (semanticScope.hasDecoration(userExpressionNode, PartialCanonicalTypeName.class)) { throw userExpressionNode.createError( new IllegalArgumentException( "cannot resolve symbol [" + semanticScope.getDecoration(userExpressionNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "]" ) ); } if (semanticScope.hasDecoration(userExpressionNode, StaticType.class)) { throw userExpressionNode.createError( new IllegalArgumentException( "value required: instead found unexpected type " + "[" + semanticScope.getDecoration(userExpressionNode, StaticType.class).getStaticCanonicalTypeName() + "]" ) ); } if (semanticScope.hasDecoration(userExpressionNode, ValueType.class) == false) { throw userExpressionNode.createError(new IllegalStateException("value required: instead found no value")); } } } /** * Visits a class. */ public void visitClass(SClass userClassNode, ScriptScope scriptScope) { for (SFunction userFunctionNode : userClassNode.getFunctionNodes()) { visitFunction(userFunctionNode, scriptScope); } } /** * Visits a function and defines variables for each parameter. * Checks: control flow, type validation */ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) { String functionName = userFunctionNode.getFunctionName(); LocalFunction localFunction = scriptScope.getFunctionTable() .getFunction(functionName, userFunctionNode.getCanonicalTypeNameParameters().size()); Class returnType = localFunction.getReturnType(); List> typeParameters = localFunction.getTypeParameters(); FunctionScope functionScope = newFunctionScope(scriptScope, localFunction.getReturnType()); for (int index = 0; index < localFunction.getTypeParameters().size(); ++index) { Class typeParameter = localFunction.getTypeParameters().get(index); String parameterName = userFunctionNode.getParameterNames().get(index); functionScope.defineVariable(userFunctionNode.getLocation(), typeParameter, parameterName, false); } SBlock userBlockNode = userFunctionNode.getBlockNode(); if (userBlockNode.getStatementNodes().isEmpty()) { throw userFunctionNode.createError( new IllegalArgumentException( "invalid function definition: " + "found no statements for function " + "[" + functionName + "] with [" + typeParameters.size() + "] parameters" ) ); } functionScope.setCondition(userBlockNode, LastSource.class); visit(userBlockNode, functionScope.newLocalScope()); boolean methodEscape = functionScope.getCondition(userBlockNode, MethodEscape.class); boolean isAutoReturnEnabled = userFunctionNode.isAutoReturnEnabled(); if (methodEscape == false && isAutoReturnEnabled == false && returnType != void.class) { throw userFunctionNode.createError( new IllegalArgumentException( "invalid function definition: " + "not all paths provide a return value for function " + "[" + functionName + "] with [" + typeParameters.size() + "] parameters" ) ); } if (methodEscape) { functionScope.setCondition(userFunctionNode, MethodEscape.class); } } /** * Visits a block and which contains one-to-many statements. * Checks: control flow */ @Override public void visitBlock(SBlock userBlockNode, SemanticScope semanticScope) { List userStatementNodes = userBlockNode.getStatementNodes(); if (userStatementNodes.isEmpty()) { throw userBlockNode.createError(new IllegalArgumentException("invalid block: found no statements")); } AStatement lastUserStatement = userStatementNodes.get(userStatementNodes.size() - 1); boolean lastSource = semanticScope.getCondition(userBlockNode, LastSource.class); boolean beginLoop = semanticScope.getCondition(userBlockNode, BeginLoop.class); boolean inLoop = semanticScope.getCondition(userBlockNode, InLoop.class); boolean lastLoop = semanticScope.getCondition(userBlockNode, LastLoop.class); boolean allEscape; boolean anyContinue = false; boolean anyBreak = false; for (AStatement userStatementNode : userStatementNodes) { if (inLoop) { semanticScope.setCondition(userStatementNode, InLoop.class); } if (userStatementNode == lastUserStatement) { if (beginLoop || lastLoop) { semanticScope.setCondition(userStatementNode, LastLoop.class); } if (lastSource) { semanticScope.setCondition(userStatementNode, LastSource.class); } } visit(userStatementNode, semanticScope); allEscape = semanticScope.getCondition(userStatementNode, AllEscape.class); if (userStatementNode == lastUserStatement) { semanticScope.replicateCondition(userStatementNode, userBlockNode, MethodEscape.class); semanticScope.replicateCondition(userStatementNode, userBlockNode, LoopEscape.class); if (allEscape) { semanticScope.setCondition(userBlockNode, AllEscape.class); } } else { if (allEscape) { throw userBlockNode.createError(new IllegalArgumentException("invalid block: unreachable statement")); } } anyContinue |= semanticScope.getCondition(userStatementNode, AnyContinue.class); anyBreak |= semanticScope.getCondition(userStatementNode, AnyBreak.class); } if (anyContinue) { semanticScope.setCondition(userBlockNode, AnyContinue.class); } if (anyBreak) { semanticScope.setCondition(userBlockNode, AnyBreak.class); } } /** * Visits an if statement with error checking for an extraneous if. * Checks: control flow */ @Override public void visitIf(SIf userIfNode, SemanticScope semanticScope) { AExpression userConditionNode = userIfNode.getConditionNode(); semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); SBlock userIfBlockNode = userIfNode.getIfBlockNode(); if (userConditionNode instanceof EBooleanConstant || userIfBlockNode == null) { throw userIfNode.createError(new IllegalArgumentException("extraneous if block")); } semanticScope.replicateCondition(userIfNode, userIfBlockNode, LastSource.class); semanticScope.replicateCondition(userIfNode, userIfBlockNode, InLoop.class); semanticScope.replicateCondition(userIfNode, userIfBlockNode, LastLoop.class); visit(userIfBlockNode, semanticScope.newLocalScope()); semanticScope.replicateCondition(userIfBlockNode, userIfNode, AnyContinue.class); semanticScope.replicateCondition(userIfBlockNode, userIfNode, AnyBreak.class); } /** * Visits an if/else statement with error checking for an extraneous if/else. * Checks: control flow */ @Override public void visitIfElse(SIfElse userIfElseNode, SemanticScope semanticScope) { AExpression userConditionNode = userIfElseNode.getConditionNode(); semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); SBlock userIfBlockNode = userIfElseNode.getIfBlockNode(); if (userConditionNode instanceof EBooleanConstant || userIfBlockNode == null) { throw userIfElseNode.createError(new IllegalArgumentException("extraneous if block")); } semanticScope.replicateCondition(userIfElseNode, userIfBlockNode, LastSource.class); semanticScope.replicateCondition(userIfElseNode, userIfBlockNode, InLoop.class); semanticScope.replicateCondition(userIfElseNode, userIfBlockNode, LastLoop.class); visit(userIfBlockNode, semanticScope.newLocalScope()); SBlock userElseBlockNode = userIfElseNode.getElseBlockNode(); if (userElseBlockNode == null) { throw userIfElseNode.createError(new IllegalArgumentException("extraneous else block.")); } semanticScope.replicateCondition(userIfElseNode, userElseBlockNode, LastSource.class); semanticScope.replicateCondition(userIfElseNode, userElseBlockNode, InLoop.class); semanticScope.replicateCondition(userIfElseNode, userElseBlockNode, LastLoop.class); visit(userElseBlockNode, semanticScope.newLocalScope()); if (semanticScope.getCondition(userIfBlockNode, MethodEscape.class) && semanticScope.getCondition(userElseBlockNode, MethodEscape.class)) { semanticScope.setCondition(userIfElseNode, MethodEscape.class); } if (semanticScope.getCondition(userIfBlockNode, LoopEscape.class) && semanticScope.getCondition(userElseBlockNode, LoopEscape.class)) { semanticScope.setCondition(userIfElseNode, LoopEscape.class); } if (semanticScope.getCondition(userIfBlockNode, AllEscape.class) && semanticScope.getCondition(userElseBlockNode, AllEscape.class)) { semanticScope.setCondition(userIfElseNode, AllEscape.class); } if (semanticScope.getCondition(userIfBlockNode, AnyContinue.class) || semanticScope.getCondition(userElseBlockNode, AnyContinue.class)) { semanticScope.setCondition(userIfElseNode, AnyContinue.class); } if (semanticScope.getCondition(userIfBlockNode, AnyBreak.class) || semanticScope.getCondition(userElseBlockNode, AnyBreak.class)) { semanticScope.setCondition(userIfElseNode, AnyBreak.class); } } /** * Visits a while statement with error checking for an extraneous loop. * Checks: control flow */ @Override public void visitWhile(SWhile userWhileNode, SemanticScope semanticScope) { semanticScope = semanticScope.newLocalScope(); AExpression userConditionNode = userWhileNode.getConditionNode(); semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); SBlock userBlockNode = userWhileNode.getBlockNode(); boolean continuous = false; if (userConditionNode instanceof EBooleanConstant) { continuous = ((EBooleanConstant) userConditionNode).getBool(); if (continuous == false) { throw userWhileNode.createError(new IllegalArgumentException("extraneous while loop")); } else { semanticScope.setCondition(userWhileNode, ContinuousLoop.class); } if (userBlockNode == null) { throw userWhileNode.createError(new IllegalArgumentException("no paths escape from while loop")); } } if (userBlockNode != null) { semanticScope.setCondition(userBlockNode, BeginLoop.class); semanticScope.setCondition(userBlockNode, InLoop.class); visit(userBlockNode, semanticScope); if (semanticScope.getCondition(userBlockNode, LoopEscape.class) && semanticScope.getCondition(userBlockNode, AnyContinue.class) == false) { throw userWhileNode.createError(new IllegalArgumentException("extraneous while loop")); } if (continuous && semanticScope.getCondition(userBlockNode, AnyBreak.class) == false) { semanticScope.setCondition(userWhileNode, MethodEscape.class); semanticScope.setCondition(userWhileNode, AllEscape.class); } } } /** * Visits a do-while statement with error checking for an extraneous loop. * Checks: control flow */ @Override public void visitDo(SDo userDoNode, SemanticScope semanticScope) { semanticScope = semanticScope.newLocalScope(); SBlock userBlockNode = userDoNode.getBlockNode(); if (userBlockNode == null) { throw userDoNode.createError(new IllegalArgumentException("extraneous do-while loop")); } semanticScope.setCondition(userBlockNode, BeginLoop.class); semanticScope.setCondition(userBlockNode, InLoop.class); visit(userBlockNode, semanticScope); if (semanticScope.getCondition(userBlockNode, LoopEscape.class) && semanticScope.getCondition(userBlockNode, AnyContinue.class) == false) { throw userDoNode.createError(new IllegalArgumentException("extraneous do-while loop")); } AExpression userConditionNode = userDoNode.getConditionNode(); semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); boolean continuous; if (userConditionNode instanceof EBooleanConstant) { continuous = ((EBooleanConstant) userConditionNode).getBool(); if (continuous == false) { throw userDoNode.createError(new IllegalArgumentException("extraneous do-while loop")); } else { semanticScope.setCondition(userDoNode, ContinuousLoop.class); } if (semanticScope.getCondition(userBlockNode, AnyBreak.class) == false) { semanticScope.setCondition(userDoNode, MethodEscape.class); semanticScope.setCondition(userDoNode, AllEscape.class); } } } /** * Visits a for statement with error checking for an extraneous loop. * Checks: control flow */ @Override public void visitFor(SFor userForNode, SemanticScope semanticScope) { semanticScope = semanticScope.newLocalScope(); ANode userInitializerNode = userForNode.getInitializerNode(); if (userInitializerNode != null) { if (userInitializerNode instanceof SDeclBlock) { visit(userInitializerNode, semanticScope); } else if (userInitializerNode instanceof AExpression) { checkedVisit((AExpression) userInitializerNode, semanticScope); } else { throw userForNode.createError(new IllegalStateException("illegal tree structure")); } } AExpression userConditionNode = userForNode.getConditionNode(); SBlock userBlockNode = userForNode.getBlockNode(); boolean continuous = false; if (userConditionNode != null) { semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); if (userConditionNode instanceof EBooleanConstant) { continuous = ((EBooleanConstant) userConditionNode).getBool(); if (continuous == false) { throw userForNode.createError(new IllegalArgumentException("extraneous for loop")); } if (userBlockNode == null) { throw userForNode.createError(new IllegalArgumentException("no paths escape from for loop")); } } } else { continuous = true; } AExpression userAfterthoughtNode = userForNode.getAfterthoughtNode(); if (userAfterthoughtNode != null) { checkedVisit(userAfterthoughtNode, semanticScope); } if (userBlockNode != null) { semanticScope.setCondition(userBlockNode, BeginLoop.class); semanticScope.setCondition(userBlockNode, InLoop.class); visit(userBlockNode, semanticScope); if (semanticScope.getCondition(userBlockNode, LoopEscape.class) && semanticScope.getCondition(userBlockNode, AnyContinue.class) == false) { throw userForNode.createError(new IllegalArgumentException("extraneous for loop")); } if (continuous && semanticScope.getCondition(userBlockNode, AnyBreak.class) == false) { semanticScope.setCondition(userForNode, MethodEscape.class); semanticScope.setCondition(userForNode, AllEscape.class); } } } /** * Visits a for-each statement which and adds an internal variable for a generated iterator. * Checks: control flow */ @Override public void visitEach(SEach userEachNode, SemanticScope semanticScope) { AExpression userIterableNode = userEachNode.getIterableNode(); semanticScope.setCondition(userIterableNode, Read.class); checkedVisit(userIterableNode, semanticScope); String canonicalTypeName = userEachNode.getCanonicalTypeName(); Class type = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (type == null) { throw userEachNode.createError( new IllegalArgumentException("invalid foreach loop: type [" + canonicalTypeName + "] not found") ); } semanticScope = semanticScope.newLocalScope(); Location location = userEachNode.getLocation(); String symbol = userEachNode.getSymbol(); Variable variable = semanticScope.defineVariable(location, type, symbol, true); semanticScope.putDecoration(userEachNode, new SemanticVariable(variable)); SBlock userBlockNode = userEachNode.getBlockNode(); if (userBlockNode == null) { throw userEachNode.createError(new IllegalArgumentException("extraneous foreach loop")); } semanticScope.setCondition(userBlockNode, BeginLoop.class); semanticScope.setCondition(userBlockNode, InLoop.class); visit(userBlockNode, semanticScope); if (semanticScope.getCondition(userBlockNode, LoopEscape.class) && semanticScope.getCondition(userBlockNode, AnyContinue.class) == false) { throw userEachNode.createError(new IllegalArgumentException("extraneous foreach loop")); } Class iterableValueType = semanticScope.getDecoration(userIterableNode, ValueType.class).getValueType(); if (iterableValueType.isArray()) { PainlessCast painlessCast = AnalyzerCaster.getLegalCast( location, iterableValueType.getComponentType(), variable.getType(), true, true ); if (painlessCast != null) { semanticScope.putDecoration(userEachNode, new ExpressionPainlessCast(painlessCast)); } } else if (iterableValueType == def.class || Iterable.class.isAssignableFrom(iterableValueType)) { if (iterableValueType != def.class) { PainlessMethod method = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(iterableValueType, false, "iterator", 0); if (method == null) { throw userEachNode.createError( new IllegalArgumentException( "invalid foreach loop: " + "method [" + typeToCanonicalTypeName(iterableValueType) + ", iterator/0] not found" ) ); } semanticScope.putDecoration(userEachNode, new IterablePainlessMethod(method)); } PainlessCast painlessCast = AnalyzerCaster.getLegalCast(location, def.class, type, true, true); if (painlessCast != null) { semanticScope.putDecoration(userEachNode, new ExpressionPainlessCast(painlessCast)); } } else { throw userEachNode.createError( new IllegalArgumentException( "invalid foreach loop: " + "cannot iterate over type [" + PainlessLookupUtility.typeToCanonicalTypeName(iterableValueType) + "]." ) ); } } /** * Visits a declaration block which contains one-to-many declarations. */ @Override public void visitDeclBlock(SDeclBlock userDeclBlockNode, SemanticScope semanticScope) { for (SDeclaration userDeclarationNode : userDeclBlockNode.getDeclarationNodes()) { visit(userDeclarationNode, semanticScope); } } /** * Visits a declaration and defines a variable with a type and optionally a value. * Checks: type validation */ @Override public void visitDeclaration(SDeclaration userDeclarationNode, SemanticScope semanticScope) { ScriptScope scriptScope = semanticScope.getScriptScope(); String symbol = userDeclarationNode.getSymbol(); if (scriptScope.getPainlessLookup().isValidCanonicalClassName(symbol)) { throw userDeclarationNode.createError( new IllegalArgumentException("invalid declaration: type [" + symbol + "] cannot be a name") ); } String canonicalTypeName = userDeclarationNode.getCanonicalTypeName(); Class type = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (type == null) { throw userDeclarationNode.createError( new IllegalArgumentException("invalid declaration: cannot resolve type [" + canonicalTypeName + "]") ); } AExpression userValueNode = userDeclarationNode.getValueNode(); if (userValueNode != null) { semanticScope.setCondition(userValueNode, Read.class); semanticScope.putDecoration(userValueNode, new TargetType(type)); checkedVisit(userValueNode, semanticScope); decorateWithCast(userValueNode, semanticScope); } Location location = userDeclarationNode.getLocation(); Variable variable = semanticScope.defineVariable(location, type, symbol, false); semanticScope.putDecoration(userDeclarationNode, new SemanticVariable(variable)); } /** * Visits a return statement and casts the value to the return type if possible. * Checks: type validation */ @Override public void visitReturn(SReturn userReturnNode, SemanticScope semanticScope) { AExpression userValueNode = userReturnNode.getValueNode(); if (userValueNode == null) { if (semanticScope.getReturnType() != void.class) { throw userReturnNode.createError( new ClassCastException( "cannot cast from " + "[" + semanticScope.getReturnCanonicalTypeName() + "] to " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(void.class) + "]" ) ); } } else { semanticScope.setCondition(userValueNode, Read.class); semanticScope.putDecoration(userValueNode, new TargetType(semanticScope.getReturnType())); semanticScope.setCondition(userValueNode, Internal.class); checkedVisit(userValueNode, semanticScope); decorateWithCast(userValueNode, semanticScope); } semanticScope.setCondition(userReturnNode, MethodEscape.class); semanticScope.setCondition(userReturnNode, LoopEscape.class); semanticScope.setCondition(userReturnNode, AllEscape.class); } /** * Visits an expression that is also considered a statement. * Checks: control flow, type validation */ @Override public void visitExpression(SExpression userExpressionNode, SemanticScope semanticScope) { Class rtnType = semanticScope.getReturnType(); boolean isVoid = rtnType == void.class; boolean lastSource = semanticScope.getCondition(userExpressionNode, LastSource.class); AExpression userStatementNode = userExpressionNode.getStatementNode(); if (lastSource && isVoid == false) { semanticScope.setCondition(userStatementNode, Read.class); } checkedVisit(userStatementNode, semanticScope); Class expressionValueType = semanticScope.getDecoration(userStatementNode, ValueType.class).getValueType(); boolean rtn = lastSource && isVoid == false && expressionValueType != void.class; if (rtn) { semanticScope.putDecoration(userStatementNode, new TargetType(rtnType)); semanticScope.setCondition(userStatementNode, Internal.class); decorateWithCast(userStatementNode, semanticScope); semanticScope.setCondition(userExpressionNode, MethodEscape.class); semanticScope.setCondition(userExpressionNode, LoopEscape.class); semanticScope.setCondition(userExpressionNode, AllEscape.class); } } /** * Visits a try statement. * Checks: control flow */ @Override public void visitTry(STry userTryNode, SemanticScope semanticScope) { SBlock userBlockNode = userTryNode.getBlockNode(); if (userBlockNode == null) { throw userTryNode.createError(new IllegalArgumentException("extraneous try statement")); } semanticScope.replicateCondition(userTryNode, userBlockNode, LastSource.class); semanticScope.replicateCondition(userTryNode, userBlockNode, InLoop.class); semanticScope.replicateCondition(userTryNode, userBlockNode, LastLoop.class); visit(userBlockNode, semanticScope.newLocalScope()); boolean methodEscape = semanticScope.getCondition(userBlockNode, MethodEscape.class); boolean loopEscape = semanticScope.getCondition(userBlockNode, LoopEscape.class); boolean allEscape = semanticScope.getCondition(userBlockNode, AllEscape.class); boolean anyContinue = semanticScope.getCondition(userBlockNode, AnyContinue.class); boolean anyBreak = semanticScope.getCondition(userBlockNode, AnyBreak.class); for (SCatch userCatchNode : userTryNode.getCatchNodes()) { semanticScope.replicateCondition(userTryNode, userCatchNode, LastSource.class); semanticScope.replicateCondition(userTryNode, userCatchNode, InLoop.class); semanticScope.replicateCondition(userTryNode, userCatchNode, LastLoop.class); visit(userCatchNode, semanticScope.newLocalScope()); methodEscape &= semanticScope.getCondition(userCatchNode, MethodEscape.class); loopEscape &= semanticScope.getCondition(userCatchNode, LoopEscape.class); allEscape &= semanticScope.getCondition(userCatchNode, AllEscape.class); anyContinue |= semanticScope.getCondition(userCatchNode, AnyContinue.class); anyBreak |= semanticScope.getCondition(userCatchNode, AnyBreak.class); } if (methodEscape) { semanticScope.setCondition(userTryNode, MethodEscape.class); } if (loopEscape) { semanticScope.setCondition(userTryNode, LoopEscape.class); } if (allEscape) { semanticScope.setCondition(userTryNode, AllEscape.class); } if (anyContinue) { semanticScope.setCondition(userTryNode, AnyContinue.class); } if (anyBreak) { semanticScope.setCondition(userTryNode, AnyBreak.class); } } /** * Visits a catch statement and defines a variable for the caught exception. * Checks: control flow, type validation */ @Override public void visitCatch(SCatch userCatchNode, SemanticScope semanticScope) { ScriptScope scriptScope = semanticScope.getScriptScope(); String symbol = userCatchNode.getSymbol(); if (scriptScope.getPainlessLookup().isValidCanonicalClassName(symbol)) { throw userCatchNode.createError( new IllegalArgumentException("invalid catch declaration: type [" + symbol + "] cannot be a name") ); } String canonicalTypeName = userCatchNode.getCanonicalTypeName(); Class type = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (type == null) { throw userCatchNode.createError( new IllegalArgumentException("invalid catch declaration: cannot resolve type [" + canonicalTypeName + "]") ); } Location location = userCatchNode.getLocation(); Variable variable = semanticScope.defineVariable(location, type, symbol, false); semanticScope.putDecoration(userCatchNode, new SemanticVariable(variable)); Class baseException = userCatchNode.getBaseException(); if (userCatchNode.getBaseException().isAssignableFrom(type) == false) { throw userCatchNode.createError( new ClassCastException( "cannot cast from [" + PainlessLookupUtility.typeToCanonicalTypeName(type) + "] " + "to [" + PainlessLookupUtility.typeToCanonicalTypeName(baseException) + "]" ) ); } SBlock userBlockNode = userCatchNode.getBlockNode(); if (userBlockNode != null) { semanticScope.replicateCondition(userCatchNode, userBlockNode, LastSource.class); semanticScope.replicateCondition(userCatchNode, userBlockNode, InLoop.class); semanticScope.replicateCondition(userCatchNode, userBlockNode, LastLoop.class); visit(userBlockNode, semanticScope); semanticScope.replicateCondition(userBlockNode, userCatchNode, MethodEscape.class); semanticScope.replicateCondition(userBlockNode, userCatchNode, LoopEscape.class); semanticScope.replicateCondition(userBlockNode, userCatchNode, AllEscape.class); semanticScope.replicateCondition(userBlockNode, userCatchNode, AnyContinue.class); semanticScope.replicateCondition(userBlockNode, userCatchNode, AnyBreak.class); } } /** * Visits a throw statement. * Checks: type validation */ @Override public void visitThrow(SThrow userThrowNode, SemanticScope semanticScope) { AExpression userExpressionNode = userThrowNode.getExpressionNode(); semanticScope.setCondition(userExpressionNode, Read.class); semanticScope.putDecoration(userExpressionNode, new TargetType(Exception.class)); checkedVisit(userExpressionNode, semanticScope); decorateWithCast(userExpressionNode, semanticScope); semanticScope.setCondition(userThrowNode, MethodEscape.class); semanticScope.setCondition(userThrowNode, LoopEscape.class); semanticScope.setCondition(userThrowNode, AllEscape.class); } /** * Visits a continue statement. * Checks: control flow */ @Override public void visitContinue(SContinue userContinueNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userContinueNode, InLoop.class) == false) { throw userContinueNode.createError(new IllegalArgumentException("invalid continue statement: not inside loop")); } if (semanticScope.getCondition(userContinueNode, LastLoop.class)) { throw userContinueNode.createError(new IllegalArgumentException("extraneous continue statement")); } semanticScope.setCondition(userContinueNode, AllEscape.class); semanticScope.setCondition(userContinueNode, AnyContinue.class); } /** * Visits a break statement. * Checks: control flow */ @Override public void visitBreak(SBreak userBreakNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userBreakNode, InLoop.class) == false) { throw userBreakNode.createError(new IllegalArgumentException("invalid break statement: not inside loop")); } semanticScope.setCondition(userBreakNode, AllEscape.class); semanticScope.setCondition(userBreakNode, LoopEscape.class); semanticScope.setCondition(userBreakNode, AnyBreak.class); } /** * Visits an assignment expression which handles both assignment and compound assignment. * Checks: type validation */ @Override public void visitAssignment(EAssignment userAssignmentNode, SemanticScope semanticScope) { AExpression userLeftNode = userAssignmentNode.getLeftNode(); semanticScope.replicateCondition(userAssignmentNode, userLeftNode, Read.class); semanticScope.setCondition(userLeftNode, Write.class); checkedVisit(userLeftNode, semanticScope); Class leftValueType = semanticScope.getDecoration(userLeftNode, Decorations.ValueType.class).getValueType(); AExpression userRightNode = userAssignmentNode.getRightNode(); semanticScope.setCondition(userRightNode, Read.class); Operation operation = userAssignmentNode.getOperation(); if (operation != null) { checkedVisit(userRightNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userRightNode, ValueType.class).getValueType(); Class compoundType; boolean isConcatenation = false; Class shiftType = null; boolean isShift = false; if (operation == Operation.MUL) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.DIV) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.REM) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.ADD) { compoundType = AnalyzerCaster.promoteAdd(leftValueType, rightValueType); isConcatenation = compoundType == String.class; } else if (operation == Operation.SUB) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.LSH) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, false); shiftType = AnalyzerCaster.promoteNumeric(rightValueType, false); isShift = true; } else if (operation == Operation.RSH) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, false); shiftType = AnalyzerCaster.promoteNumeric(rightValueType, false); isShift = true; } else if (operation == Operation.USH) { compoundType = AnalyzerCaster.promoteNumeric(leftValueType, false); shiftType = AnalyzerCaster.promoteNumeric(rightValueType, false); isShift = true; } else if (operation == Operation.BWAND) { compoundType = AnalyzerCaster.promoteXor(leftValueType, rightValueType); } else if (operation == Operation.XOR) { compoundType = AnalyzerCaster.promoteXor(leftValueType, rightValueType); } else if (operation == Operation.BWOR) { compoundType = AnalyzerCaster.promoteXor(leftValueType, rightValueType); } else { throw userAssignmentNode.createError(new IllegalStateException("illegal tree structure")); } if (compoundType == null || (isShift && shiftType == null)) { throw userAssignmentNode.createError( new ClassCastException( "invalid compound assignment: " + "cannot apply [" + operation.symbol + "=] to types [" + leftValueType + "] and [" + rightValueType + "]" ) ); } if (isConcatenation) { semanticScope.putDecoration(userRightNode, new TargetType(rightValueType)); } else if (isShift) { if (compoundType == def.class) { // shifts are promoted independently, but for the def type, we need object. semanticScope.putDecoration(userRightNode, new TargetType(def.class)); } else if (shiftType == long.class) { semanticScope.putDecoration(userRightNode, new TargetType(int.class)); semanticScope.setCondition(userRightNode, Explicit.class); } else { semanticScope.putDecoration(userRightNode, new TargetType(shiftType)); } } else { semanticScope.putDecoration(userRightNode, new TargetType(compoundType)); } decorateWithCast(userRightNode, semanticScope); Location location = userAssignmentNode.getLocation(); PainlessCast upcast = AnalyzerCaster.getLegalCast(location, leftValueType, compoundType, false, false); PainlessCast downcast = AnalyzerCaster.getLegalCast(location, compoundType, leftValueType, true, false); semanticScope.putDecoration(userAssignmentNode, new CompoundType(compoundType)); if (upcast != null) { semanticScope.putDecoration(userAssignmentNode, new UpcastPainlessCast(upcast)); } if (downcast != null) { semanticScope.putDecoration(userAssignmentNode, new DowncastPainlessCast(downcast)); } // if the lhs node is a def optimized node we update the actual type to remove the need for a cast } else if (semanticScope.getCondition(userLeftNode, DefOptimized.class)) { checkedVisit(userRightNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userRightNode, ValueType.class).getValueType(); if (rightValueType == void.class) { throw userAssignmentNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign type [" + PainlessLookupUtility.typeToCanonicalTypeName(void.class) + "]" ) ); } semanticScope.putDecoration(userLeftNode, new ValueType(rightValueType)); leftValueType = rightValueType; // Otherwise, we must adapt the rhs type to the lhs type with a cast. } else { semanticScope.putDecoration(userRightNode, new TargetType(leftValueType)); checkedVisit(userRightNode, semanticScope); decorateWithCast(userRightNode, semanticScope); } semanticScope.putDecoration( userAssignmentNode, new ValueType(semanticScope.getCondition(userAssignmentNode, Read.class) ? leftValueType : void.class) ); } /** * Visits a unary expression which special-cases a negative operator when the child * is a constant expression to handle the maximum negative values appropriately. * Checks: type validation */ @Override public void visitUnary(EUnary userUnaryNode, SemanticScope semanticScope) { Operation operation = userUnaryNode.getOperation(); if (semanticScope.getCondition(userUnaryNode, Write.class)) { throw userUnaryNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } if (semanticScope.getCondition(userUnaryNode, Read.class) == false) { throw userUnaryNode.createError( new IllegalArgumentException( "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } AExpression userChildNode = userUnaryNode.getChildNode(); Class valueType; Class unaryType = null; if (operation == Operation.SUB && (userChildNode instanceof ENumeric || userChildNode instanceof EDecimal)) { semanticScope.setCondition(userChildNode, Read.class); semanticScope.copyDecoration(userUnaryNode, userChildNode, TargetType.class); semanticScope.replicateCondition(userUnaryNode, userChildNode, Explicit.class); semanticScope.replicateCondition(userUnaryNode, userChildNode, Internal.class); semanticScope.setCondition(userChildNode, Negate.class); checkedVisit(userChildNode, semanticScope); if (semanticScope.hasDecoration(userUnaryNode, TargetType.class)) { decorateWithCast(userChildNode, semanticScope); } valueType = semanticScope.getDecoration(userChildNode, ValueType.class).getValueType(); } else { if (operation == Operation.NOT) { semanticScope.setCondition(userChildNode, Read.class); semanticScope.putDecoration(userChildNode, new TargetType(boolean.class)); checkedVisit(userChildNode, semanticScope); decorateWithCast(userChildNode, semanticScope); valueType = boolean.class; } else if (operation == Operation.BWNOT || operation == Operation.ADD || operation == Operation.SUB) { semanticScope.setCondition(userChildNode, Read.class); checkedVisit(userChildNode, semanticScope); Class childValueType = semanticScope.getDecoration(userChildNode, ValueType.class).getValueType(); unaryType = AnalyzerCaster.promoteNumeric(childValueType, operation != Operation.BWNOT); if (unaryType == null) { throw userUnaryNode.createError( new ClassCastException( "cannot apply the " + operation.name + " operator " + "[" + operation.symbol + "] to the type " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(childValueType) + "]" ) ); } semanticScope.putDecoration(userChildNode, new TargetType(unaryType)); decorateWithCast(userChildNode, semanticScope); TargetType targetType = semanticScope.getDecoration(userUnaryNode, TargetType.class); if (unaryType == def.class && targetType != null) { valueType = targetType.getTargetType(); } else { valueType = unaryType; } } else { throw userUnaryNode.createError(new IllegalStateException("unexpected unary operation [" + operation.name + "]")); } } semanticScope.putDecoration(userUnaryNode, new ValueType(valueType)); if (unaryType != null) { semanticScope.putDecoration(userUnaryNode, new UnaryType(unaryType)); } } /** * Visits a binary expression which covers all the mathematical operators. * Checks: type validation */ @Override public void visitBinary(EBinary userBinaryNode, SemanticScope semanticScope) { Operation operation = userBinaryNode.getOperation(); if (semanticScope.getCondition(userBinaryNode, Write.class)) { throw userBinaryNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } if (semanticScope.getCondition(userBinaryNode, Read.class) == false) { throw userBinaryNode.createError( new IllegalArgumentException( "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } AExpression userLeftNode = userBinaryNode.getLeftNode(); semanticScope.setCondition(userLeftNode, Read.class); checkedVisit(userLeftNode, semanticScope); Class leftValueType = semanticScope.getDecoration(userLeftNode, ValueType.class).getValueType(); AExpression userRightNode = userBinaryNode.getRightNode(); semanticScope.setCondition(userRightNode, Read.class); checkedVisit(userRightNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userRightNode, ValueType.class).getValueType(); Class valueType; Class binaryType; Class shiftType = null; if (operation == Operation.FIND || operation == Operation.MATCH) { semanticScope.putDecoration(userLeftNode, new TargetType(String.class)); semanticScope.putDecoration(userRightNode, new TargetType(Pattern.class)); decorateWithCast(userLeftNode, semanticScope); decorateWithCast(userRightNode, semanticScope); binaryType = boolean.class; valueType = boolean.class; } else { if (operation == Operation.MUL || operation == Operation.DIV || operation == Operation.REM) { binaryType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.ADD) { binaryType = AnalyzerCaster.promoteAdd(leftValueType, rightValueType); } else if (operation == Operation.SUB) { binaryType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) { binaryType = AnalyzerCaster.promoteNumeric(leftValueType, false); shiftType = AnalyzerCaster.promoteNumeric(rightValueType, false); if (shiftType == null) { binaryType = null; } } else if (operation == Operation.BWOR || operation == Operation.BWAND) { binaryType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, false); } else if (operation == Operation.XOR) { binaryType = AnalyzerCaster.promoteXor(leftValueType, rightValueType); } else { throw userBinaryNode.createError(new IllegalStateException("unexpected binary operation [" + operation.name + "]")); } if (binaryType == null) { throw userBinaryNode.createError( new ClassCastException( "cannot apply the " + operation.name + " operator " + "[" + operation.symbol + "] to the types " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]" ) ); } valueType = binaryType; if (binaryType == def.class || shiftType == def.class) { TargetType targetType = semanticScope.getDecoration(userBinaryNode, TargetType.class); if (targetType != null) { valueType = targetType.getTargetType(); } } else if (operation != Operation.ADD || binaryType != String.class) { semanticScope.putDecoration(userLeftNode, new TargetType(binaryType)); if (operation == Operation.LSH || operation == Operation.RSH || operation == Operation.USH) { if (shiftType == long.class) { semanticScope.putDecoration(userRightNode, new TargetType(int.class)); semanticScope.setCondition(userRightNode, Explicit.class); } else { semanticScope.putDecoration(userRightNode, new TargetType(shiftType)); } } else { semanticScope.putDecoration(userRightNode, new TargetType(binaryType)); } decorateWithCast(userLeftNode, semanticScope); decorateWithCast(userRightNode, semanticScope); } } semanticScope.putDecoration(userBinaryNode, new ValueType(valueType)); semanticScope.putDecoration(userBinaryNode, new BinaryType(binaryType)); if (shiftType != null) { semanticScope.putDecoration(userBinaryNode, new ShiftType(shiftType)); } } /** * Visits a boolean comp expression which covers boolean comparision operators. * Checks: type validation */ @Override public void visitBooleanComp(EBooleanComp userBooleanCompNode, SemanticScope semanticScope) { Operation operation = userBooleanCompNode.getOperation(); if (semanticScope.getCondition(userBooleanCompNode, Write.class)) { throw userBooleanCompNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } if (semanticScope.getCondition(userBooleanCompNode, Read.class) == false) { throw userBooleanCompNode.createError( new IllegalArgumentException( "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } AExpression userLeftNode = userBooleanCompNode.getLeftNode(); semanticScope.setCondition(userLeftNode, Read.class); semanticScope.putDecoration(userLeftNode, new TargetType(boolean.class)); checkedVisit(userLeftNode, semanticScope); decorateWithCast(userLeftNode, semanticScope); AExpression userRightNode = userBooleanCompNode.getRightNode(); semanticScope.setCondition(userRightNode, Read.class); semanticScope.putDecoration(userRightNode, new TargetType(boolean.class)); checkedVisit(userRightNode, semanticScope); decorateWithCast(userRightNode, semanticScope); semanticScope.putDecoration(userBooleanCompNode, new ValueType(boolean.class)); } /** * Visits a comp expression which covers mathematical comparision operators. * Checks: type validation */ @Override public void visitComp(EComp userCompNode, SemanticScope semanticScope) { Operation operation = userCompNode.getOperation(); if (semanticScope.getCondition(userCompNode, Write.class)) { throw userCompNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } if (semanticScope.getCondition(userCompNode, Read.class) == false) { throw userCompNode.createError( new IllegalArgumentException( "not a statement: result not used from " + operation.name + " operation " + "[" + operation.symbol + "]" ) ); } AExpression userLeftNode = userCompNode.getLeftNode(); semanticScope.setCondition(userLeftNode, Read.class); checkedVisit(userLeftNode, semanticScope); Class leftValueType = semanticScope.getDecoration(userLeftNode, ValueType.class).getValueType(); AExpression userRightNode = userCompNode.getRightNode(); semanticScope.setCondition(userRightNode, Read.class); checkedVisit(userRightNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userRightNode, ValueType.class).getValueType(); Class promotedType; if (operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER) { promotedType = AnalyzerCaster.promoteEquality(leftValueType, rightValueType); } else if (operation == Operation.GT || operation == Operation.GTE || operation == Operation.LT || operation == Operation.LTE) { promotedType = AnalyzerCaster.promoteNumeric(leftValueType, rightValueType, true); } else { throw userCompNode.createError(new IllegalStateException("unexpected binary operation [" + operation.name + "]")); } if (promotedType == null) { throw userCompNode.createError( new ClassCastException( "cannot apply the " + operation.name + " operator " + "[" + operation.symbol + "] to the types " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]" ) ); } if ((operation == Operation.EQ || operation == Operation.EQR || operation == Operation.NE || operation == Operation.NER) && userLeftNode instanceof ENull && userRightNode instanceof ENull) { throw userCompNode.createError(new IllegalArgumentException("extraneous comparison of [null] constants")); } if (operation == Operation.EQR || operation == Operation.NER || promotedType != def.class) { semanticScope.putDecoration(userLeftNode, new TargetType(promotedType)); semanticScope.putDecoration(userRightNode, new TargetType(promotedType)); decorateWithCast(userLeftNode, semanticScope); decorateWithCast(userRightNode, semanticScope); } semanticScope.putDecoration(userCompNode, new ValueType(boolean.class)); semanticScope.putDecoration(userCompNode, new ComparisonType(promotedType)); } /** * Visits an explicit expression which handles an explicit cast. * Checks: type validation */ @Override public void visitExplicit(EExplicit userExplicitNode, SemanticScope semanticScope) { String canonicalTypeName = userExplicitNode.getCanonicalTypeName(); if (semanticScope.getCondition(userExplicitNode, Write.class)) { throw userExplicitNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to an explicit cast with target type [" + canonicalTypeName + "]" ) ); } if (semanticScope.getCondition(userExplicitNode, Read.class) == false) { throw userExplicitNode.createError( new IllegalArgumentException( "not a statement: result not used from explicit cast with target type [" + canonicalTypeName + "]" ) ); } Class valueType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (valueType == null) { throw userExplicitNode.createError(new IllegalArgumentException("cannot resolve type [" + canonicalTypeName + "]")); } AExpression userChildNode = userExplicitNode.getChildNode(); semanticScope.setCondition(userChildNode, Read.class); semanticScope.putDecoration(userChildNode, new TargetType(valueType)); semanticScope.setCondition(userChildNode, Explicit.class); checkedVisit(userChildNode, semanticScope); decorateWithCast(userChildNode, semanticScope); semanticScope.putDecoration(userExplicitNode, new ValueType(valueType)); } /** * Visits an instanceof expression which handles both primitive and non-primitive types. * Checks: type validation */ @Override public void visitInstanceof(EInstanceof userInstanceofNode, SemanticScope semanticScope) { String canonicalTypeName = userInstanceofNode.getCanonicalTypeName(); if (semanticScope.getCondition(userInstanceofNode, Write.class)) { throw userInstanceofNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to instanceof with target type [" + canonicalTypeName + "]" ) ); } if (semanticScope.getCondition(userInstanceofNode, Read.class) == false) { throw userInstanceofNode.createError( new IllegalArgumentException( "not a statement: result not used from instanceof with target type [" + canonicalTypeName + "]" ) ); } Class instanceType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (instanceType == null) { throw userInstanceofNode.createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "].")); } AExpression userExpressionNode = userInstanceofNode.getExpressionNode(); semanticScope.setCondition(userExpressionNode, Read.class); checkedVisit(userExpressionNode, semanticScope); semanticScope.putDecoration(userInstanceofNode, new ValueType(boolean.class)); semanticScope.putDecoration(userInstanceofNode, new InstanceType(instanceType)); } /** * Visits a conditional expression. * Checks: type validation */ @Override public void visitConditional(EConditional userConditionalNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userConditionalNode, Write.class)) { throw userConditionalNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to conditional operation [?:]") ); } if (semanticScope.getCondition(userConditionalNode, Read.class) == false) { throw userConditionalNode.createError( new IllegalArgumentException("not a statement: result not used from conditional operation [?:]") ); } AExpression userConditionNode = userConditionalNode.getConditionNode(); semanticScope.setCondition(userConditionNode, Read.class); semanticScope.putDecoration(userConditionNode, new TargetType(boolean.class)); checkedVisit(userConditionNode, semanticScope); decorateWithCast(userConditionNode, semanticScope); AExpression userTrueNode = userConditionalNode.getTrueNode(); semanticScope.setCondition(userTrueNode, Read.class); semanticScope.copyDecoration(userConditionalNode, userTrueNode, TargetType.class); semanticScope.replicateCondition(userConditionalNode, userTrueNode, Explicit.class); semanticScope.replicateCondition(userConditionalNode, userTrueNode, Internal.class); checkedVisit(userTrueNode, semanticScope); Class leftValueType = semanticScope.getDecoration(userTrueNode, ValueType.class).getValueType(); AExpression userFalseNode = userConditionalNode.getFalseNode(); semanticScope.setCondition(userFalseNode, Read.class); semanticScope.copyDecoration(userConditionalNode, userFalseNode, TargetType.class); semanticScope.replicateCondition(userConditionalNode, userFalseNode, Explicit.class); semanticScope.replicateCondition(userConditionalNode, userFalseNode, Internal.class); checkedVisit(userFalseNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userFalseNode, ValueType.class).getValueType(); TargetType targetType = semanticScope.getDecoration(userConditionalNode, TargetType.class); Class valueType; if (targetType == null) { Class promote = AnalyzerCaster.promoteConditional(leftValueType, rightValueType); if (promote == null) { throw userConditionalNode.createError( new ClassCastException( "cannot apply the conditional operator [?:] to the types " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(leftValueType) + "] and " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(rightValueType) + "]" ) ); } semanticScope.putDecoration(userTrueNode, new TargetType(promote)); semanticScope.putDecoration(userFalseNode, new TargetType(promote)); valueType = promote; } else { valueType = targetType.getTargetType(); } decorateWithCast(userTrueNode, semanticScope); decorateWithCast(userFalseNode, semanticScope); semanticScope.putDecoration(userConditionalNode, new ValueType(valueType)); } /** * Visits a elvis expression which is a shortcut for a null check on a conditional expression. * Checks: type validation */ @Override public void visitElvis(EElvis userElvisNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userElvisNode, Write.class)) { throw userElvisNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to elvis operation [?:]") ); } if (semanticScope.getCondition(userElvisNode, Read.class) == false) { throw userElvisNode.createError(new IllegalArgumentException("not a statement: result not used from elvis operation [?:]")); } TargetType targetType = semanticScope.getDecoration(userElvisNode, TargetType.class); if (targetType != null && targetType.getTargetType().isPrimitive()) { throw userElvisNode.createError(new IllegalArgumentException("Elvis operator cannot return primitives")); } AExpression userLeftNode = userElvisNode.getLeftNode(); semanticScope.setCondition(userLeftNode, Read.class); semanticScope.copyDecoration(userElvisNode, userLeftNode, TargetType.class); semanticScope.replicateCondition(userElvisNode, userLeftNode, Explicit.class); semanticScope.replicateCondition(userElvisNode, userLeftNode, Internal.class); checkedVisit(userLeftNode, semanticScope); Class leftValueType = semanticScope.getDecoration(userLeftNode, ValueType.class).getValueType(); AExpression userRightNode = userElvisNode.getRightNode(); semanticScope.setCondition(userRightNode, Read.class); semanticScope.copyDecoration(userElvisNode, userRightNode, TargetType.class); semanticScope.replicateCondition(userElvisNode, userRightNode, Explicit.class); semanticScope.replicateCondition(userElvisNode, userRightNode, Internal.class); checkedVisit(userRightNode, semanticScope); Class rightValueType = semanticScope.getDecoration(userRightNode, ValueType.class).getValueType(); if (userLeftNode instanceof ENull) { throw userElvisNode.createError(new IllegalArgumentException("Extraneous elvis operator. LHS is null.")); } if (userLeftNode instanceof EBooleanConstant || userLeftNode instanceof ENumeric || userLeftNode instanceof EDecimal || userLeftNode instanceof EString) { throw userElvisNode.createError(new IllegalArgumentException("Extraneous elvis operator. LHS is a constant.")); } if (leftValueType.isPrimitive()) { throw userElvisNode.createError(new IllegalArgumentException("Extraneous elvis operator. LHS is a primitive.")); } if (userRightNode instanceof ENull) { throw userElvisNode.createError(new IllegalArgumentException("Extraneous elvis operator. RHS is null.")); } Class valueType; if (targetType == null) { Class promote = AnalyzerCaster.promoteConditional(leftValueType, rightValueType); semanticScope.putDecoration(userLeftNode, new TargetType(promote)); semanticScope.putDecoration(userRightNode, new TargetType(promote)); valueType = promote; } else { valueType = targetType.getTargetType(); } decorateWithCast(userLeftNode, semanticScope); decorateWithCast(userRightNode, semanticScope); semanticScope.putDecoration(userElvisNode, new ValueType(valueType)); } /** * Visits a list init expression which is a shortcut for initializing a list with pre-defined values. * Checks: type validation */ @Override public void visitListInit(EListInit userListInitNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userListInitNode, Write.class)) { throw userListInitNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to list initializer") ); } if (semanticScope.getCondition(userListInitNode, Read.class) == false) { throw userListInitNode.createError(new IllegalArgumentException("not a statement: result not used from list initializer")); } Class valueType = ArrayList.class; PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(valueType, 0); if (constructor == null) { throw userListInitNode.createError( new IllegalArgumentException("constructor [" + typeToCanonicalTypeName(valueType) + ", /0] not found") ); } semanticScope.putDecoration(userListInitNode, new StandardPainlessConstructor(constructor)); PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(valueType, false, "add", 1); if (method == null) { throw userListInitNode.createError( new IllegalArgumentException("method [" + typeToCanonicalTypeName(valueType) + ", add/1] not found") ); } semanticScope.putDecoration(userListInitNode, new StandardPainlessMethod(method)); for (AExpression userValueNode : userListInitNode.getValueNodes()) { semanticScope.setCondition(userValueNode, Read.class); semanticScope.putDecoration(userValueNode, new TargetType(def.class)); semanticScope.setCondition(userValueNode, Internal.class); checkedVisit(userValueNode, semanticScope); decorateWithCast(userValueNode, semanticScope); } semanticScope.putDecoration(userListInitNode, new ValueType(valueType)); } /** * Visits a map init expression which is a shortcut for initializing a map with pre-defined keys and values. * Checks: type validation */ @Override public void visitMapInit(EMapInit userMapInitNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userMapInitNode, Write.class)) { throw userMapInitNode.createError(new IllegalArgumentException("invalid assignment: cannot assign a value to map initializer")); } if (semanticScope.getCondition(userMapInitNode, Read.class) == false) { throw userMapInitNode.createError(new IllegalArgumentException("not a statement: result not used from map initializer")); } Class valueType = HashMap.class; PainlessConstructor constructor = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessConstructor(valueType, 0); if (constructor == null) { throw userMapInitNode.createError( new IllegalArgumentException("constructor [" + typeToCanonicalTypeName(valueType) + ", /0] not found") ); } semanticScope.putDecoration(userMapInitNode, new StandardPainlessConstructor(constructor)); PainlessMethod method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod(valueType, false, "put", 2); if (method == null) { throw userMapInitNode.createError( new IllegalArgumentException("method [" + typeToCanonicalTypeName(valueType) + ", put/2] not found") ); } semanticScope.putDecoration(userMapInitNode, new StandardPainlessMethod(method)); List userKeyNodes = userMapInitNode.getKeyNodes(); List userValueNodes = userMapInitNode.getValueNodes(); if (userKeyNodes.size() != userValueNodes.size()) { throw userMapInitNode.createError(new IllegalStateException("Illegal tree structure.")); } for (int i = 0; i < userKeyNodes.size(); ++i) { AExpression userKeyNode = userKeyNodes.get(i); semanticScope.setCondition(userKeyNode, Read.class); semanticScope.putDecoration(userKeyNode, new TargetType(def.class)); semanticScope.setCondition(userKeyNode, Internal.class); checkedVisit(userKeyNode, semanticScope); decorateWithCast(userKeyNode, semanticScope); AExpression userValueNode = userValueNodes.get(i); semanticScope.setCondition(userValueNode, Read.class); semanticScope.putDecoration(userValueNode, new TargetType(def.class)); semanticScope.setCondition(userValueNode, Internal.class); checkedVisit(userValueNode, semanticScope); decorateWithCast(userValueNode, semanticScope); } semanticScope.putDecoration(userMapInitNode, new ValueType(valueType)); } /** * Visits a list init expression which either defines a standard array or initializes * a single-dimensional with pre-defined values. * Checks: type validation */ @Override public void visitNewArray(ENewArray userNewArrayNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userNewArrayNode, Write.class)) { throw userNewArrayNode.createError(new IllegalArgumentException("invalid assignment: cannot assign a value to new array")); } if (semanticScope.getCondition(userNewArrayNode, Read.class) == false) { throw userNewArrayNode.createError(new IllegalArgumentException("not a statement: result not used from new array")); } String canonicalTypeName = userNewArrayNode.getCanonicalTypeName(); Class valueType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (valueType == null) { throw userNewArrayNode.createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "].")); } for (AExpression userValueNode : userNewArrayNode.getValueNodes()) { semanticScope.setCondition(userValueNode, Read.class); semanticScope.putDecoration( userValueNode, new TargetType(userNewArrayNode.isInitializer() ? valueType.getComponentType() : int.class) ); semanticScope.setCondition(userValueNode, Internal.class); checkedVisit(userValueNode, semanticScope); decorateWithCast(userValueNode, semanticScope); } semanticScope.putDecoration(userNewArrayNode, new ValueType(valueType)); } /** * Visits a new obj expression which creates a new object and calls its constructor. * Checks: type validation */ @Override public void visitNewObj(ENewObj userNewObjNode, SemanticScope semanticScope) { String canonicalTypeName = userNewObjNode.getCanonicalTypeName(); List userArgumentNodes = userNewObjNode.getArgumentNodes(); int userArgumentsSize = userArgumentNodes.size(); if (semanticScope.getCondition(userNewObjNode, Write.class)) { throw userNewObjNode.createError( new IllegalArgumentException( "invalid assignment cannot assign a value to new object with constructor " + "[" + canonicalTypeName + "/" + userArgumentsSize + "]" ) ); } ScriptScope scriptScope = semanticScope.getScriptScope(); Class valueType = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); if (valueType == null) { throw userNewObjNode.createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "].")); } PainlessConstructor constructor = scriptScope.getPainlessLookup().lookupPainlessConstructor(valueType, userArgumentsSize); if (constructor == null) { throw userNewObjNode.createError( new IllegalArgumentException( "constructor [" + typeToCanonicalTypeName(valueType) + ", /" + userArgumentsSize + "] not found" ) ); } scriptScope.putDecoration(userNewObjNode, new StandardPainlessConstructor(constructor)); scriptScope.markNonDeterministic(constructor.annotations.containsKey(NonDeterministicAnnotation.class)); Class[] types = new Class[constructor.typeParameters.size()]; constructor.typeParameters.toArray(types); if (constructor.typeParameters.size() != userArgumentsSize) { throw userNewObjNode.createError( new IllegalArgumentException( "When calling constructor on type [" + PainlessLookupUtility.typeToCanonicalTypeName(valueType) + "] " + "expected [" + constructor.typeParameters.size() + "] arguments, but found [" + userArgumentsSize + "]." ) ); } for (int i = 0; i < userArgumentsSize; ++i) { AExpression userArgumentNode = userArgumentNodes.get(i); semanticScope.setCondition(userArgumentNode, Read.class); semanticScope.putDecoration(userArgumentNode, new TargetType(types[i])); semanticScope.setCondition(userArgumentNode, Internal.class); checkedVisit(userArgumentNode, semanticScope); decorateWithCast(userArgumentNode, semanticScope); } semanticScope.putDecoration(userNewObjNode, new ValueType(valueType)); } /** * Visits a call local expression which is a method call with no qualifier (prefix). * Checks: type validation, method resolution */ @Override public void visitCallLocal(ECallLocal userCallLocalNode, SemanticScope semanticScope) { String methodName = userCallLocalNode.getMethodName(); List userArgumentNodes = userCallLocalNode.getArgumentNodes(); int userArgumentsSize = userArgumentNodes.size(); if (semanticScope.getCondition(userCallLocalNode, Write.class)) { throw userCallLocalNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to function call [" + methodName + "/" + userArgumentsSize + "]" ) ); } ScriptScope scriptScope = semanticScope.getScriptScope(); FunctionTable.LocalFunction localFunction = null; PainlessMethod importedMethod = null; PainlessClassBinding classBinding = null; int classBindingOffset = 0; PainlessInstanceBinding instanceBinding = null; Class valueType; localFunction = scriptScope.getFunctionTable().getFunction(methodName, userArgumentsSize); // user cannot call internal functions, reset to null if an internal function is found if (localFunction != null && localFunction.isInternal()) { localFunction = null; } if (localFunction == null) { importedMethod = scriptScope.getPainlessLookup().lookupImportedPainlessMethod(methodName, userArgumentsSize); if (importedMethod == null) { classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize); // check to see if this class binding requires an implicit this reference if (classBinding != null && classBinding.typeParameters.isEmpty() == false && classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { classBinding = null; } if (classBinding == null) { // This extra check looks for a possible match where the class binding requires an implicit this // reference. This is a temporary solution to allow the class binding access to data from the // base script class without need for a user to add additional arguments. A long term solution // will likely involve adding a class instance binding where any instance can have a class binding // as part of its API. However, the situation at run-time is difficult and will modifications that // are a substantial change if even possible to do. classBinding = scriptScope.getPainlessLookup().lookupPainlessClassBinding(methodName, userArgumentsSize + 1); if (classBinding != null) { if (classBinding.typeParameters.isEmpty() == false && classBinding.typeParameters.get(0) == scriptScope.getScriptClassInfo().getBaseClass()) { classBindingOffset = 1; } else { classBinding = null; } } if (classBinding == null) { instanceBinding = scriptScope.getPainlessLookup().lookupPainlessInstanceBinding(methodName, userArgumentsSize); if (instanceBinding == null) { throw userCallLocalNode.createError( new IllegalArgumentException( "Unknown call [" + methodName + "] with [" + userArgumentNodes + "] arguments." ) ); } } } } } List> typeParameters; if (localFunction != null) { semanticScope.putDecoration(userCallLocalNode, new StandardLocalFunction(localFunction)); typeParameters = new ArrayList<>(localFunction.getTypeParameters()); valueType = localFunction.getReturnType(); } else if (importedMethod != null) { semanticScope.putDecoration(userCallLocalNode, new StandardPainlessMethod(importedMethod)); scriptScope.markNonDeterministic(importedMethod.annotations.containsKey(NonDeterministicAnnotation.class)); typeParameters = new ArrayList<>(importedMethod.typeParameters); valueType = importedMethod.returnType; } else if (classBinding != null) { semanticScope.putDecoration(userCallLocalNode, new StandardPainlessClassBinding(classBinding)); semanticScope.putDecoration(userCallLocalNode, new StandardConstant(classBindingOffset)); scriptScope.markNonDeterministic(classBinding.annotations.containsKey(NonDeterministicAnnotation.class)); typeParameters = new ArrayList<>(classBinding.typeParameters); valueType = classBinding.returnType; } else if (instanceBinding != null) { semanticScope.putDecoration(userCallLocalNode, new StandardPainlessInstanceBinding(instanceBinding)); typeParameters = new ArrayList<>(instanceBinding.typeParameters); valueType = instanceBinding.returnType; } else { throw new IllegalStateException("Illegal tree structure."); } // if the class binding is using an implicit this reference then the arguments counted must // be incremented by 1 as the this reference will not be part of the arguments passed into // the class binding call for (int argument = 0; argument < userArgumentsSize; ++argument) { AExpression userArgumentNode = userArgumentNodes.get(argument); semanticScope.setCondition(userArgumentNode, Read.class); semanticScope.putDecoration(userArgumentNode, new TargetType(typeParameters.get(argument + classBindingOffset))); semanticScope.setCondition(userArgumentNode, Internal.class); checkedVisit(userArgumentNode, semanticScope); decorateWithCast(userArgumentNode, semanticScope); } semanticScope.putDecoration(userCallLocalNode, new ValueType(valueType)); } /** * Visits a boolean constant expression. * Checks: type validation */ @Override public void visitBooleanConstant(EBooleanConstant userBooleanConstantNode, SemanticScope semanticScope) { boolean bool = userBooleanConstantNode.getBool(); if (semanticScope.getCondition(userBooleanConstantNode, Write.class)) { throw userBooleanConstantNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to boolean constant [" + bool + "]") ); } if (semanticScope.getCondition(userBooleanConstantNode, Read.class) == false) { throw userBooleanConstantNode.createError( new IllegalArgumentException("not a statement: boolean constant [" + bool + "] not used") ); } semanticScope.putDecoration(userBooleanConstantNode, new ValueType(boolean.class)); semanticScope.putDecoration(userBooleanConstantNode, new StandardConstant(bool)); } /** * Visits a numeric constant expression which includes int and long. * Checks: type validation */ @Override public void visitNumeric(ENumeric userNumericNode, SemanticScope semanticScope) { String numeric = userNumericNode.getNumeric(); if (semanticScope.getCondition(userNumericNode, Negate.class)) { numeric = "-" + numeric; } if (semanticScope.getCondition(userNumericNode, Write.class)) { throw userNumericNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to numeric constant [" + numeric + "]") ); } if (semanticScope.getCondition(userNumericNode, Read.class) == false) { throw userNumericNode.createError(new IllegalArgumentException("not a statement: numeric constant [" + numeric + "] not used")); } int radix = userNumericNode.getRadix(); Class valueType; Object constant; if (numeric.endsWith("d") || numeric.endsWith("D")) { if (radix != 10) { throw userNumericNode.createError(new IllegalStateException("Illegal tree structure.")); } try { constant = Double.parseDouble(numeric.substring(0, numeric.length() - 1)); valueType = double.class; } catch (NumberFormatException exception) { throw userNumericNode.createError(new IllegalArgumentException("Invalid double constant [" + numeric + "].")); } } else if (numeric.endsWith("f") || numeric.endsWith("F")) { if (radix != 10) { throw userNumericNode.createError(new IllegalStateException("Illegal tree structure.")); } try { constant = Float.parseFloat(numeric.substring(0, numeric.length() - 1)); valueType = float.class; } catch (NumberFormatException exception) { throw userNumericNode.createError(new IllegalArgumentException("Invalid float constant [" + numeric + "].")); } } else if (numeric.endsWith("l") || numeric.endsWith("L")) { try { constant = Long.parseLong(numeric.substring(0, numeric.length() - 1), radix); valueType = long.class; } catch (NumberFormatException exception) { throw userNumericNode.createError(new IllegalArgumentException("Invalid long constant [" + numeric + "].")); } } else { try { TargetType targetType = semanticScope.getDecoration(userNumericNode, TargetType.class); Class sort = targetType == null ? int.class : targetType.getTargetType(); int integer = Integer.parseInt(numeric, radix); if (sort == byte.class && integer >= Byte.MIN_VALUE && integer <= Byte.MAX_VALUE) { constant = (byte) integer; valueType = byte.class; } else if (sort == char.class && integer >= Character.MIN_VALUE && integer <= Character.MAX_VALUE) { constant = (char) integer; valueType = char.class; } else if (sort == short.class && integer >= Short.MIN_VALUE && integer <= Short.MAX_VALUE) { constant = (short) integer; valueType = short.class; } else { constant = integer; valueType = int.class; } } catch (NumberFormatException exception) { try { // Check if we can parse as a long. If so then hint that the user might prefer that. Long.parseLong(numeric, radix); throw userNumericNode.createError( new IllegalArgumentException( "Invalid int constant [" + numeric + "]. If you want a long constant then change it to [" + numeric + "L]." ) ); } catch (NumberFormatException longNoGood) { // Ignored } throw userNumericNode.createError(new IllegalArgumentException("Invalid int constant [" + numeric + "].")); } } semanticScope.putDecoration(userNumericNode, new ValueType(valueType)); semanticScope.putDecoration(userNumericNode, new StandardConstant(constant)); } /** * Visits a decimal constant expression which includes float and double. * Checks: type validation */ @Override public void visitDecimal(EDecimal userDecimalNode, SemanticScope semanticScope) { String decimal = userDecimalNode.getDecimal(); if (semanticScope.getCondition(userDecimalNode, Negate.class)) { decimal = "-" + decimal; } if (semanticScope.getCondition(userDecimalNode, Write.class)) { throw userDecimalNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to decimal constant [" + decimal + "]") ); } if (semanticScope.getCondition(userDecimalNode, Read.class) == false) { throw userDecimalNode.createError(new IllegalArgumentException("not a statement: decimal constant [" + decimal + "] not used")); } Class valueType; Object constant; if (decimal.endsWith("f") || decimal.endsWith("F")) { try { constant = Float.parseFloat(decimal.substring(0, decimal.length() - 1)); valueType = float.class; } catch (NumberFormatException exception) { throw userDecimalNode.createError(new IllegalArgumentException("Invalid float constant [" + decimal + "].")); } } else { String toParse = decimal; if (toParse.endsWith("d") || decimal.endsWith("D")) { toParse = toParse.substring(0, decimal.length() - 1); } try { constant = Double.parseDouble(toParse); valueType = double.class; } catch (NumberFormatException exception) { throw userDecimalNode.createError(new IllegalArgumentException("Invalid double constant [" + decimal + "].")); } } semanticScope.putDecoration(userDecimalNode, new ValueType(valueType)); semanticScope.putDecoration(userDecimalNode, new StandardConstant(constant)); } /** * Visits a string constant expression. * Checks: type validation */ @Override public void visitString(EString userStringNode, SemanticScope semanticScope) { String string = userStringNode.getString(); if (semanticScope.getCondition(userStringNode, Write.class)) { throw userStringNode.createError( new IllegalArgumentException("invalid assignment: cannot assign a value to string constant [" + string + "]") ); } if (semanticScope.getCondition(userStringNode, Read.class) == false) { throw userStringNode.createError(new IllegalArgumentException("not a statement: string constant [" + string + "] not used")); } semanticScope.putDecoration(userStringNode, new ValueType(String.class)); semanticScope.putDecoration(userStringNode, new StandardConstant(string)); } /** * Visits a null constant expression. * Checks: type validation */ @Override public void visitNull(ENull userNullNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userNullNode, Write.class)) { throw userNullNode.createError(new IllegalArgumentException("invalid assignment: cannot assign a value to null constant")); } if (semanticScope.getCondition(userNullNode, Read.class) == false) { throw userNullNode.createError(new IllegalArgumentException("not a statement: null constant not used")); } TargetType targetType = semanticScope.getDecoration(userNullNode, TargetType.class); Class valueType; if (targetType != null) { if (targetType.getTargetType().isPrimitive()) { throw userNullNode.createError( new IllegalArgumentException("Cannot cast null to a primitive type [" + targetType.getTargetCanonicalTypeName() + "].") ); } valueType = targetType.getTargetType(); } else { valueType = Object.class; } semanticScope.putDecoration(userNullNode, new ValueType(valueType)); } /** * Visits a regex constant expression which does additional work to initialize a regex using pre-defined flags. * Checks: type validation */ @Override public void visitRegex(ERegex userRegexNode, SemanticScope semanticScope) { String pattern = userRegexNode.getPattern(); String flags = userRegexNode.getFlags(); if (semanticScope.getCondition(userRegexNode, Write.class)) { throw userRegexNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to regex constant [" + pattern + "] with flags [" + flags + "]" ) ); } if (semanticScope.getCondition(userRegexNode, Read.class) == false) { throw userRegexNode.createError( new IllegalArgumentException("not a statement: regex constant [" + pattern + "] with flags [" + flags + "] not used") ); } if (semanticScope.getScriptScope().getCompilerSettings().areRegexesEnabled() == CompilerSettings.RegexEnabled.FALSE) { throw userRegexNode.createError( new IllegalStateException( "Regexes are disabled. Set [script.painless.regex.enabled] to [true] " + "in opensearch.yaml to allow them. Be careful though, regexes break out of Painless's protection against deep " + "recursion and long loops." ) ); } Location location = userRegexNode.getLocation(); int constant = 0; for (int i = 0; i < flags.length(); ++i) { char flag = flags.charAt(i); switch (flag) { case 'c': constant |= Pattern.CANON_EQ; break; case 'i': constant |= Pattern.CASE_INSENSITIVE; break; case 'l': constant |= Pattern.LITERAL; break; case 'm': constant |= Pattern.MULTILINE; break; case 's': constant |= Pattern.DOTALL; break; case 'U': constant |= Pattern.UNICODE_CHARACTER_CLASS; break; case 'u': constant |= Pattern.UNICODE_CASE; break; case 'x': constant |= Pattern.COMMENTS; break; default: throw new IllegalArgumentException("invalid regular expression: unknown flag [" + flag + "]"); } } try { Pattern.compile(pattern, constant); } catch (PatternSyntaxException pse) { throw new Location(location.getSourceName(), location.getOffset() + 1 + pse.getIndex()).createError( new IllegalArgumentException( "invalid regular expression: " + "could not compile regex constant [" + pattern + "] with flags [" + flags + "]", pse ) ); } semanticScope.putDecoration(userRegexNode, new ValueType(Pattern.class)); semanticScope.putDecoration(userRegexNode, new StandardConstant(constant)); } /** * Visits a lambda expression which adds a new internal method to the class as defined by the lambda. * Checks: control flow, type validation */ @Override public void visitLambda(ELambda userLambdaNode, SemanticScope semanticScope) { if (semanticScope.getCondition(userLambdaNode, Write.class)) { throw userLambdaNode.createError(new IllegalArgumentException("invalid assignment: cannot assign a value to a lambda")); } if (semanticScope.getCondition(userLambdaNode, Read.class) == false) { throw userLambdaNode.createError(new IllegalArgumentException("not a statement: lambda not used")); } ScriptScope scriptScope = semanticScope.getScriptScope(); TargetType targetType = semanticScope.getDecoration(userLambdaNode, TargetType.class); List canonicalTypeNameParameters = userLambdaNode.getCanonicalTypeNameParameters(); Class returnType; List> typeParameters; PainlessMethod interfaceMethod; // inspect the target first, set interface method if we know it. if (targetType == null) { // we don't know anything: treat as def returnType = def.class; // don't infer any types, replace any null types with def typeParameters = new ArrayList<>(canonicalTypeNameParameters.size()); for (String type : canonicalTypeNameParameters) { if (type == null) { typeParameters.add(def.class); } else { Class typeParameter = scriptScope.getPainlessLookup().canonicalTypeNameToType(type); if (typeParameter == null) { throw userLambdaNode.createError(new IllegalArgumentException("cannot resolve type [" + type + "]")); } typeParameters.add(typeParameter); } } } else { // we know the method statically, infer return type and any unknown/def types interfaceMethod = scriptScope.getPainlessLookup().lookupFunctionalInterfacePainlessMethod(targetType.getTargetType()); if (interfaceMethod == null) { throw userLambdaNode.createError( new IllegalArgumentException( "Cannot pass lambda to " + "[" + targetType.getTargetCanonicalTypeName() + "], not a functional interface" ) ); } // check arity before we manipulate parameters if (interfaceMethod.typeParameters.size() != canonicalTypeNameParameters.size()) throw new IllegalArgumentException( "Incorrect number of parameters for [" + interfaceMethod.javaMethod.getName() + "] in [" + targetType.getTargetCanonicalTypeName() + "]" ); // for method invocation, its allowed to ignore the return value if (interfaceMethod.returnType == void.class) { returnType = def.class; } else { returnType = interfaceMethod.returnType; } // replace any null types with the actual type typeParameters = new ArrayList<>(canonicalTypeNameParameters.size()); for (int i = 0; i < canonicalTypeNameParameters.size(); i++) { String paramType = canonicalTypeNameParameters.get(i); if (paramType == null) { typeParameters.add(interfaceMethod.typeParameters.get(i)); } else { Class typeParameter = scriptScope.getPainlessLookup().canonicalTypeNameToType(paramType); if (typeParameter == null) { throw userLambdaNode.createError(new IllegalArgumentException("cannot resolve type [" + paramType + "]")); } typeParameters.add(typeParameter); } } } Location location = userLambdaNode.getLocation(); List parameterNames = userLambdaNode.getParameterNames(); LambdaScope lambdaScope = semanticScope.newLambdaScope(returnType); for (int index = 0; index < typeParameters.size(); ++index) { Class type = typeParameters.get(index); String parameterName = parameterNames.get(index); lambdaScope.defineVariable(location, type, parameterName, true); } SBlock userBlockNode = userLambdaNode.getBlockNode(); if (userBlockNode.getStatementNodes().isEmpty()) { throw userLambdaNode.createError(new IllegalArgumentException("cannot generate empty lambda")); } semanticScope.setCondition(userBlockNode, LastSource.class); visit(userBlockNode, lambdaScope); if (semanticScope.getCondition(userBlockNode, MethodEscape.class) == false) { throw userLambdaNode.createError(new IllegalArgumentException("not all paths return a value for lambda")); } // prepend capture list to lambda's arguments List capturedVariables = new ArrayList<>(lambdaScope.getCaptures()); List> typeParametersWithCaptures = new ArrayList<>(capturedVariables.size() + typeParameters.size()); List parameterNamesWithCaptures = new ArrayList<>(capturedVariables.size() + parameterNames.size()); for (Variable capturedVariable : capturedVariables) { typeParametersWithCaptures.add(capturedVariable.getType()); parameterNamesWithCaptures.add(capturedVariable.getName()); } typeParametersWithCaptures.addAll(typeParameters); parameterNamesWithCaptures.addAll(parameterNames); // desugar lambda body into a synthetic method String name = scriptScope.getNextSyntheticName("lambda"); scriptScope.getFunctionTable().addFunction(name, returnType, typeParametersWithCaptures, true, true); Class valueType; // setup method reference to synthetic method if (targetType == null) { String defReferenceEncoding = "Sthis." + name + "," + capturedVariables.size(); valueType = String.class; semanticScope.putDecoration(userLambdaNode, new EncodingDecoration(defReferenceEncoding)); } else { FunctionRef ref = FunctionRef.create( scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), location, targetType.getTargetType(), "this", name, capturedVariables.size(), scriptScope.getCompilerSettings().asMap() ); valueType = targetType.getTargetType(); semanticScope.putDecoration(userLambdaNode, new ReferenceDecoration(ref)); } semanticScope.putDecoration(userLambdaNode, new ValueType(valueType)); semanticScope.putDecoration(userLambdaNode, new MethodNameDecoration(name)); semanticScope.putDecoration(userLambdaNode, new ReturnType(returnType)); semanticScope.putDecoration(userLambdaNode, new TypeParameters(typeParametersWithCaptures)); semanticScope.putDecoration(userLambdaNode, new ParameterNames(parameterNamesWithCaptures)); semanticScope.putDecoration(userLambdaNode, new CapturesDecoration(capturedVariables)); } /** * Visits a function ref expression which covers class function references, * constructor function references, and local function references. * Checks: type validation */ @Override public void visitFunctionRef(EFunctionRef userFunctionRefNode, SemanticScope semanticScope) { ScriptScope scriptScope = semanticScope.getScriptScope(); Location location = userFunctionRefNode.getLocation(); String symbol = userFunctionRefNode.getSymbol(); String methodName = userFunctionRefNode.getMethodName(); boolean read = semanticScope.getCondition(userFunctionRefNode, Read.class); Class type = scriptScope.getPainlessLookup().canonicalTypeNameToType(symbol); TargetType targetType = semanticScope.getDecoration(userFunctionRefNode, TargetType.class); Class valueType; if (symbol.equals("this") || type != null) { if (semanticScope.getCondition(userFunctionRefNode, Write.class)) { throw userFunctionRefNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to function reference [" + symbol + ":" + methodName + "]" ) ); } if (read == false) { throw userFunctionRefNode.createError( new IllegalArgumentException("not a statement: function reference [" + symbol + ":" + methodName + "] not used") ); } if (targetType == null) { valueType = String.class; String defReferenceEncoding = "S" + symbol + "." + methodName + ",0"; semanticScope.putDecoration(userFunctionRefNode, new EncodingDecoration(defReferenceEncoding)); } else { FunctionRef ref = FunctionRef.create( scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), location, targetType.getTargetType(), symbol, methodName, 0, scriptScope.getCompilerSettings().asMap() ); valueType = targetType.getTargetType(); semanticScope.putDecoration(userFunctionRefNode, new ReferenceDecoration(ref)); } } else { if (semanticScope.getCondition(userFunctionRefNode, Write.class)) { throw userFunctionRefNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to capturing function reference [" + symbol + ":" + methodName + "]" ) ); } if (read == false) { throw userFunctionRefNode.createError( new IllegalArgumentException( "not a statement: capturing function reference [" + symbol + ":" + methodName + "] not used" ) ); } SemanticScope.Variable captured = semanticScope.getVariable(location, symbol); semanticScope.putDecoration(userFunctionRefNode, new CapturesDecoration(Collections.singletonList(captured))); if (targetType == null) { String defReferenceEncoding; if (captured.getType() == def.class) { // dynamic implementation defReferenceEncoding = "D" + symbol + "." + methodName + ",1"; } else { // typed implementation defReferenceEncoding = "S" + captured.getCanonicalTypeName() + "." + methodName + ",1"; } valueType = String.class; semanticScope.putDecoration(userFunctionRefNode, new EncodingDecoration(defReferenceEncoding)); } else { valueType = targetType.getTargetType(); // static case if (captured.getType() != def.class) { FunctionRef ref = FunctionRef.create( scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), location, targetType.getTargetType(), captured.getCanonicalTypeName(), methodName, 1, scriptScope.getCompilerSettings().asMap() ); semanticScope.putDecoration(userFunctionRefNode, new ReferenceDecoration(ref)); } } } semanticScope.putDecoration(userFunctionRefNode, new ValueType(valueType)); } /** * Visits a new array function ref expression which covers only a new array function reference * and generates an internal method to define the new array. * Checks: type validation */ @Override public void visitNewArrayFunctionRef(ENewArrayFunctionRef userNewArrayFunctionRefNode, SemanticScope semanticScope) { String canonicalTypeName = userNewArrayFunctionRefNode.getCanonicalTypeName(); if (semanticScope.getCondition(userNewArrayFunctionRefNode, Write.class)) { throw userNewArrayFunctionRefNode.createError( new IllegalArgumentException( "cannot assign a value to new array function reference with target type [ + " + canonicalTypeName + "]" ) ); } if (semanticScope.getCondition(userNewArrayFunctionRefNode, Read.class) == false) { throw userNewArrayFunctionRefNode.createError( new IllegalArgumentException( "not a statement: new array function reference with target type [" + canonicalTypeName + "] not used" ) ); } ScriptScope scriptScope = semanticScope.getScriptScope(); TargetType targetType = semanticScope.getDecoration(userNewArrayFunctionRefNode, TargetType.class); Class valueType; Class clazz = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); semanticScope.putDecoration(userNewArrayFunctionRefNode, new ReturnType(clazz)); if (clazz == null) { throw userNewArrayFunctionRefNode.createError(new IllegalArgumentException("Not a type [" + canonicalTypeName + "].")); } String name = scriptScope.getNextSyntheticName("newarray"); scriptScope.getFunctionTable().addFunction(name, clazz, Collections.singletonList(int.class), true, true); semanticScope.putDecoration(userNewArrayFunctionRefNode, new MethodNameDecoration(name)); if (targetType == null) { String defReferenceEncoding = "Sthis." + name + ",0"; valueType = String.class; scriptScope.putDecoration(userNewArrayFunctionRefNode, new EncodingDecoration(defReferenceEncoding)); } else { FunctionRef ref = FunctionRef.create( scriptScope.getPainlessLookup(), scriptScope.getFunctionTable(), userNewArrayFunctionRefNode.getLocation(), targetType.getTargetType(), "this", name, 0, scriptScope.getCompilerSettings().asMap() ); valueType = targetType.getTargetType(); semanticScope.putDecoration(userNewArrayFunctionRefNode, new ReferenceDecoration(ref)); } semanticScope.putDecoration(userNewArrayFunctionRefNode, new ValueType(valueType)); } /** * Visits a symbol expression which covers static types, partial canonical types, * and variables. * Checks: type checking, type resolution, variable resolution */ @Override public void visitSymbol(ESymbol userSymbolNode, SemanticScope semanticScope) { boolean read = semanticScope.getCondition(userSymbolNode, Read.class); boolean write = semanticScope.getCondition(userSymbolNode, Write.class); String symbol = userSymbolNode.getSymbol(); Class staticType = semanticScope.getScriptScope().getPainlessLookup().canonicalTypeNameToType(symbol); if (staticType != null) { if (write) { throw userSymbolNode.createError( new IllegalArgumentException( "invalid assignment: " + "cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "]" ) ); } if (read == false) { throw userSymbolNode.createError( new IllegalArgumentException( "not a statement: " + "static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "] not used" ) ); } semanticScope.putDecoration(userSymbolNode, new StaticType(staticType)); } else if (semanticScope.isVariableDefined(symbol)) { if (read == false && write == false) { throw userSymbolNode.createError(new IllegalArgumentException("not a statement: variable [" + symbol + "] not used")); } Location location = userSymbolNode.getLocation(); Variable variable = semanticScope.getVariable(location, symbol); if (write && variable.isFinal()) { throw userSymbolNode.createError(new IllegalArgumentException("Variable [" + variable.getName() + "] is read-only.")); } Class valueType = variable.getType(); semanticScope.putDecoration(userSymbolNode, new ValueType(valueType)); } else { semanticScope.putDecoration(userSymbolNode, new PartialCanonicalTypeName(symbol)); } } /** * Visits a dot expression which is a field index with a qualifier (prefix) and * may resolve to a static type, a partial canonical type, a field, a shortcut to a * getter/setter method on a type, or a getter/setter for a Map or List. * Checks: type validation, method resolution, field resolution */ @Override public void visitDot(EDot userDotNode, SemanticScope semanticScope) { boolean read = semanticScope.getCondition(userDotNode, Read.class); boolean write = semanticScope.getCondition(userDotNode, Write.class); if (read == false && write == false) { throw userDotNode.createError(new IllegalArgumentException("not a statement: result of dot operator [.] not used")); } ScriptScope scriptScope = semanticScope.getScriptScope(); String index = userDotNode.getIndex(); AExpression userPrefixNode = userDotNode.getPrefixNode(); semanticScope.setCondition(userPrefixNode, Read.class); visit(userPrefixNode, semanticScope); ValueType prefixValueType = semanticScope.getDecoration(userPrefixNode, ValueType.class); StaticType prefixStaticType = semanticScope.getDecoration(userPrefixNode, StaticType.class); if (prefixValueType != null && prefixStaticType != null) { throw userDotNode.createError( new IllegalStateException( "cannot have both " + "value [" + prefixValueType.getValueCanonicalTypeName() + "] " + "and type [" + prefixStaticType.getStaticCanonicalTypeName() + "]" ) ); } if (semanticScope.hasDecoration(userPrefixNode, PartialCanonicalTypeName.class)) { if (prefixValueType != null) { throw userDotNode.createError( new IllegalArgumentException( "value required: instead found unexpected type " + "[" + prefixValueType.getValueCanonicalTypeName() + "]" ) ); } if (prefixStaticType != null) { throw userDotNode.createError( new IllegalArgumentException( "value required: instead found unexpected type " + "[" + prefixStaticType.getStaticType() + "]" ) ); } String canonicalTypeName = semanticScope.getDecoration(userPrefixNode, PartialCanonicalTypeName.class) .getPartialCanonicalTypeName() + "." + index; Class staticType = scriptScope.getPainlessLookup().canonicalTypeNameToType(canonicalTypeName); 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(staticType) + "]" ) ); } semanticScope.putDecoration(userDotNode, new StaticType(staticType)); } } else { Class staticType = null; if (prefixStaticType != null) { String staticCanonicalTypeName = prefixStaticType.getStaticCanonicalTypeName() + "." + userDotNode.getIndex(); staticType = scriptScope.getPainlessLookup().canonicalTypeNameToType(staticCanonicalTypeName); } if (staticType != null) { if (write) { throw userDotNode.createError( new IllegalArgumentException( "invalid assignment: " + "cannot write a value to a static type [" + PainlessLookupUtility.typeToCanonicalTypeName(staticType) + "]" ) ); } 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"); } } } } } /** * Visits a brace expression which is an array index with a qualifier (prefix) and * may resolve to an array index, or a getter/setter for a Map or List. * Checks: type validation, method resolution, field resolution */ @Override public void visitBrace(EBrace userBraceNode, SemanticScope semanticScope) { boolean read = semanticScope.getCondition(userBraceNode, Read.class); boolean write = semanticScope.getCondition(userBraceNode, Write.class); if (read == false && write == false) { throw userBraceNode.createError(new IllegalArgumentException("not a statement: result of brace operator not used")); } AExpression userPrefixNode = userBraceNode.getPrefixNode(); semanticScope.setCondition(userPrefixNode, Read.class); checkedVisit(userPrefixNode, semanticScope); Class prefixValueType = semanticScope.getDecoration(userPrefixNode, ValueType.class).getValueType(); AExpression userIndexNode = userBraceNode.getIndexNode(); Class valueType; if (prefixValueType.isArray()) { semanticScope.setCondition(userIndexNode, Read.class); semanticScope.putDecoration(userIndexNode, new TargetType(int.class)); checkedVisit(userIndexNode, semanticScope); decorateWithCast(userIndexNode, semanticScope); valueType = prefixValueType.getComponentType(); } else if (prefixValueType == def.class) { semanticScope.setCondition(userIndexNode, Read.class); checkedVisit(userIndexNode, semanticScope); TargetType targetType = semanticScope.getDecoration(userBraceNode, TargetType.class); // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class || semanticScope.getCondition(userBraceNode, Explicit.class) ? def.class : targetType.getTargetType(); if (write) { semanticScope.setCondition(userBraceNode, DefOptimized.class); } } else if (Map.class.isAssignableFrom(prefixValueType)) { String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType); PainlessMethod getter = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixValueType, false, "get", 1); PainlessMethod setter = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixValueType, false, "put", 2); if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1)) { throw userBraceNode.createError( new IllegalArgumentException("Illegal map get shortcut for type [" + canonicalClassName + "].") ); } if (setter != null && setter.typeParameters.size() != 2) { throw userBraceNode.createError( new IllegalArgumentException("Illegal map set shortcut for type [" + canonicalClassName + "].") ); } if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) || getter.returnType.equals(setter.typeParameters.get(1)) == false)) { throw userBraceNode.createError(new IllegalArgumentException("Shortcut argument types must match.")); } if ((read == false || getter != null) && (write == false || setter != null)) { semanticScope.setCondition(userIndexNode, Read.class); semanticScope.putDecoration( userIndexNode, new TargetType(setter != null ? setter.typeParameters.get(0) : getter.typeParameters.get(0)) ); checkedVisit(userIndexNode, semanticScope); decorateWithCast(userIndexNode, semanticScope); valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType; if (getter != null) { semanticScope.putDecoration(userBraceNode, new GetterPainlessMethod(getter)); } if (setter != null) { semanticScope.putDecoration(userBraceNode, new SetterPainlessMethod(setter)); } } else { throw userBraceNode.createError( new IllegalArgumentException("Illegal map shortcut for type [" + canonicalClassName + "].") ); } semanticScope.setCondition(userBraceNode, MapShortcut.class); } else if (List.class.isAssignableFrom(prefixValueType)) { String canonicalClassName = PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType); PainlessMethod getter = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixValueType, false, "get", 1); PainlessMethod setter = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixValueType, false, "set", 2); if (getter != null && (getter.returnType == void.class || getter.typeParameters.size() != 1 || getter.typeParameters.get(0) != int.class)) { throw userBraceNode.createError( new IllegalArgumentException("Illegal list get shortcut for type [" + canonicalClassName + "].") ); } if (setter != null && (setter.typeParameters.size() != 2 || setter.typeParameters.get(0) != int.class)) { throw userBraceNode.createError( new IllegalArgumentException("Illegal list set shortcut for type [" + canonicalClassName + "].") ); } if (getter != null && setter != null && (!getter.typeParameters.get(0).equals(setter.typeParameters.get(0)) || !getter.returnType.equals(setter.typeParameters.get(1)))) { throw userBraceNode.createError(new IllegalArgumentException("Shortcut argument types must match.")); } if ((read == false || getter != null) && (write == false || setter != null)) { semanticScope.setCondition(userIndexNode, Read.class); semanticScope.putDecoration(userIndexNode, new TargetType(int.class)); checkedVisit(userIndexNode, semanticScope); decorateWithCast(userIndexNode, semanticScope); valueType = setter != null ? setter.typeParameters.get(1) : getter.returnType; if (getter != null) { semanticScope.putDecoration(userBraceNode, new GetterPainlessMethod(getter)); } if (setter != null) { semanticScope.putDecoration(userBraceNode, new SetterPainlessMethod(setter)); } } else { throw userBraceNode.createError( new IllegalArgumentException("Illegal list shortcut for type [" + canonicalClassName + "].") ); } semanticScope.setCondition(userBraceNode, ListShortcut.class); } else { throw userBraceNode.createError( new IllegalArgumentException( "Illegal array access on type " + "[" + PainlessLookupUtility.typeToCanonicalTypeName(prefixValueType) + "]." ) ); } semanticScope.putDecoration(userBraceNode, new ValueType(valueType)); } /** * Visits a call expression which is a method call with a qualifier (prefix). * Checks: type validation, method resolution */ @Override public void visitCall(ECall userCallNode, SemanticScope semanticScope) { String methodName = userCallNode.getMethodName(); List userArgumentNodes = userCallNode.getArgumentNodes(); int userArgumentsSize = userArgumentNodes.size(); if (semanticScope.getCondition(userCallNode, Write.class)) { throw userCallNode.createError( new IllegalArgumentException( "invalid assignment: cannot assign a value to method call [" + methodName + "/" + userArgumentsSize + "]" ) ); } AExpression userPrefixNode = userCallNode.getPrefixNode(); semanticScope.setCondition(userPrefixNode, Read.class); visit(userPrefixNode, semanticScope); ValueType prefixValueType = semanticScope.getDecoration(userPrefixNode, ValueType.class); StaticType prefixStaticType = semanticScope.getDecoration(userPrefixNode, StaticType.class); if (prefixValueType != null && prefixStaticType != null) { throw userCallNode.createError( new IllegalStateException( "cannot have both " + "value [" + prefixValueType.getValueCanonicalTypeName() + "] " + "and type [" + prefixStaticType.getStaticCanonicalTypeName() + "]" ) ); } if (semanticScope.hasDecoration(userPrefixNode, PartialCanonicalTypeName.class)) { throw userCallNode.createError( new IllegalArgumentException( "cannot resolve symbol " + "[" + semanticScope.getDecoration(userPrefixNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "]" ) ); } Class valueType; if (prefixValueType != null && prefixValueType.getValueType() == def.class) { for (AExpression userArgumentNode : userArgumentNodes) { semanticScope.setCondition(userArgumentNode, Read.class); semanticScope.setCondition(userArgumentNode, Internal.class); checkedVisit(userArgumentNode, semanticScope); Class argumentValueType = semanticScope.getDecoration(userArgumentNode, ValueType.class).getValueType(); if (argumentValueType == void.class) { throw userCallNode.createError( new IllegalArgumentException("Argument(s) cannot be of [void] type when calling method [" + methodName + "].") ); } } TargetType targetType = userCallNode.isNullSafe() ? null : semanticScope.getDecoration(userCallNode, TargetType.class); // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class || semanticScope.getCondition(userCallNode, Explicit.class) ? def.class : targetType.getTargetType(); } else { PainlessMethod method; if (prefixValueType != null) { method = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixValueType.getValueType(), false, methodName, userArgumentsSize); if (method == null) { throw userCallNode.createError( new IllegalArgumentException( "member method " + "[" + prefixValueType.getValueCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + "not found" ) ); } } else if (prefixStaticType != null) { method = semanticScope.getScriptScope() .getPainlessLookup() .lookupPainlessMethod(prefixStaticType.getStaticType(), true, methodName, userArgumentsSize); if (method == null) { throw userCallNode.createError( new IllegalArgumentException( "static method " + "[" + prefixStaticType.getStaticCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + "not found" ) ); } } else { throw userCallNode.createError(new IllegalStateException("value required: instead found no value")); } semanticScope.getScriptScope().markNonDeterministic(method.annotations.containsKey(NonDeterministicAnnotation.class)); for (int argument = 0; argument < userArgumentsSize; ++argument) { AExpression userArgumentNode = userArgumentNodes.get(argument); semanticScope.setCondition(userArgumentNode, Read.class); semanticScope.putDecoration(userArgumentNode, new TargetType(method.typeParameters.get(argument))); semanticScope.setCondition(userArgumentNode, Internal.class); checkedVisit(userArgumentNode, semanticScope); decorateWithCast(userArgumentNode, semanticScope); } semanticScope.putDecoration(userCallNode, new StandardPainlessMethod(method)); valueType = method.returnType; } if (userCallNode.isNullSafe() && valueType.isPrimitive()) { throw new IllegalArgumentException("Result of null safe operator must be nullable"); } semanticScope.putDecoration(userCallNode, new ValueType(valueType)); } }