Skip to content

Commit

Permalink
Script: Decorate global variables in User Tree
Browse files Browse the repository at this point in the history
Adds `GlobalMember` decoration to the User Tree for
variables which have name conflicts with global variables.

Parameter `isLocalMember` specifies if the variable
shadows the global name.

Refs: elastic#69742
  • Loading branch information
stu-elastic committed Apr 27, 2021
1 parent eaa59fb commit 4084734
Show file tree
Hide file tree
Showing 10 changed files with 410 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ public class ScriptClassInfo {
public final List<FunctionTable.LocalFunction> converters;
public final FunctionTable.LocalFunction defConverter;

public static final String MAIN_METHOD = "execute";

public ScriptClassInfo(PainlessLookup painlessLookup, Class<?> baseClass) {
this.baseClass = baseClass;

Expand All @@ -53,7 +55,7 @@ public ScriptClassInfo(PainlessLookup painlessLookup, Class<?> baseClass) {
if (m.isDefault()) {
continue;
}
if (m.getName().equals("execute")) {
if (m.getName().equals(MAIN_METHOD)) {
if (executeMethod == null) {
executeMethod = m;
returnType = m.getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.phase.IRTreeVisitor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -55,6 +57,10 @@ public <T extends IRDecoration<?>> T getDecoration(Class<T> type) {
return type.cast(decorations.get(type));
}

public List<IRDecoration<?>> getAllDecorations() {
return new ArrayList<>(decorations.values());
}

public <T extends IRDecoration<V>, V> V getDecorationValue(Class<T> type) {
return getDecorationValueOrDefault(type, null);
}
Expand Down Expand Up @@ -89,6 +95,10 @@ public boolean hasCondition(Class<? extends IRCondition> type) {
return conditions.contains(type);
}

public List<Class<? extends IRCondition>> getAllConditions() {
return new ArrayList<>(conditions);
}

/* ---- end conditions, begin node data ---- */

private final Location location;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.ScriptClassInfo;
import org.elasticsearch.painless.lookup.PainlessCast;
import org.elasticsearch.painless.lookup.PainlessClassBinding;
import org.elasticsearch.painless.lookup.PainlessConstructor;
Expand Down Expand Up @@ -205,22 +206,24 @@ public void checkedVisit(AExpression userExpressionNode, SemanticScope semanticS
* Visits a class.
*/
public void visitClass(SClass userClassNode, ScriptScope scriptScope) {
SemanticScope.ClassScope classScope = new SemanticScope.ClassScope(scriptScope);
for (SFunction userFunctionNode : userClassNode.getFunctionNodes()) {
visitFunction(userFunctionNode, scriptScope);
visitFunction(userFunctionNode, classScope);
}
}

/**
* Visits a function and defines variables for each parameter.
* Checks: control flow, type validation
*/
public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {
public void visitFunction(SFunction userFunctionNode, SemanticScope.ClassScope classScope) {
String functionName = userFunctionNode.getFunctionName();
ScriptScope scriptScope = classScope.getScriptScope();
LocalFunction localFunction =
scriptScope.getFunctionTable().getFunction(functionName, userFunctionNode.getCanonicalTypeNameParameters().size());
Class<?> returnType = localFunction.getReturnType();
List<Class<?>> typeParameters = localFunction.getTypeParameters();
FunctionScope functionScope = newFunctionScope(scriptScope, localFunction.getReturnType());
FunctionScope functionScope = newFunctionScope(classScope, localFunction);

for (int index = 0; index < localFunction.getTypeParameters().size(); ++index) {
Class<?> typeParameter = localFunction.getTypeParameters().get(index);
Expand Down Expand Up @@ -2403,6 +2406,9 @@ public void visitSymbol(ESymbol userSymbolNode, SemanticScope semanticScope) {

Class<?> valueType = variable.getType();
semanticScope.putDecoration(userSymbolNode, new ValueType(valueType));
semanticScope.putDecoration(userSymbolNode, new Decorations.GlobalMember(
ScriptClassInfo.MAIN_METHOD.equals(semanticScope.getLocalFunction().getFunctionName()))
);
} else {
semanticScope.putDecoration(userSymbolNode, new PartialCanonicalTypeName(symbol));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.elasticsearch.painless.node.AExpression;
import org.elasticsearch.painless.node.AStatement;
import org.elasticsearch.painless.node.SBlock;
import org.elasticsearch.painless.node.SClass;
import org.elasticsearch.painless.node.SExpression;
import org.elasticsearch.painless.node.SFunction;
import org.elasticsearch.painless.node.SReturn;
Expand All @@ -36,38 +37,56 @@

import java.util.List;

import static org.elasticsearch.painless.symbol.SemanticScope.newFunctionScope;
import static org.elasticsearch.painless.symbol.SemanticScope.newMainFunctionScope;

public class PainlessSemanticAnalysisPhase extends DefaultSemanticAnalysisPhase {

/** Current function while in {@code visitFunction} */
protected String functionName = "";

@Override
public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {
public void visitClass(SClass userClassNode, ScriptScope scriptScope) {
SemanticScope.ClassScope classScope = new SemanticScope.ClassScope(scriptScope);
for (SFunction userFunctionNode : userClassNode.getFunctionNodes()) {
// Hit execute first to capture the globals
if (ScriptClassInfo.MAIN_METHOD.equals(userFunctionNode.getFunctionName())) {
visitFunction(userFunctionNode, classScope);
break;
}
}
for (SFunction userFunctionNode : userClassNode.getFunctionNodes()) {
if (ScriptClassInfo.MAIN_METHOD.equals(userFunctionNode.getFunctionName()) == false) {
visitFunction(userFunctionNode, classScope);
}
}
}

@Override
public void visitFunction(SFunction userFunctionNode, SemanticScope.ClassScope classScope) {
functionName = userFunctionNode.getFunctionName();
ScriptScope scriptScope = classScope.getScriptScope();

if ("execute".equals(functionName)) {
if (ScriptClassInfo.MAIN_METHOD.equals(functionName)) {
ScriptClassInfo scriptClassInfo = scriptScope.getScriptClassInfo();
LocalFunction localFunction =
scriptScope.getFunctionTable().getFunction(functionName, scriptClassInfo.getExecuteArguments().size());
List<Class<?>> typeParameters = localFunction.getTypeParameters();
FunctionScope functionScope = newFunctionScope(scriptScope, localFunction.getReturnType());

for (int i = 0; i < typeParameters.size(); ++i) {
Class<?> typeParameter = localFunction.getTypeParameters().get(i);
String parameterName = scriptClassInfo.getExecuteArguments().get(i).getName();
functionScope.defineVariable(userFunctionNode.getLocation(), typeParameter, parameterName, false);
classScope.defineVariable(userFunctionNode.getLocation(), typeParameter, parameterName, false);
}

for (int i = 0; i < scriptClassInfo.getGetMethods().size(); ++i) {
Class<?> typeParameter = scriptClassInfo.getGetReturns().get(i);
org.objectweb.asm.commons.Method method = scriptClassInfo.getGetMethods().get(i);
String parameterName = method.getName().substring(3);
parameterName = Character.toLowerCase(parameterName.charAt(0)) + parameterName.substring(1);
functionScope.defineVariable(userFunctionNode.getLocation(), typeParameter, parameterName, false);
classScope.defineVariable(userFunctionNode.getLocation(), typeParameter, parameterName, false);
}

FunctionScope functionScope = newMainFunctionScope(classScope, localFunction);
SBlock userBlockNode = userFunctionNode.getBlockNode();

if (userBlockNode.getStatementNodes().isEmpty()) {
Expand All @@ -86,7 +105,7 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {

scriptScope.setUsedVariables(functionScope.getUsedVariables());
} else {
super.visitFunction(userFunctionNode, scriptScope);
super.visitFunction(userFunctionNode, classScope);
}

functionName = "";
Expand Down Expand Up @@ -117,7 +136,7 @@ public void visitExpression(SExpression userExpressionNode, SemanticScope semant
if (rtn) {
semanticScope.putDecoration(userStatementNode, new TargetType(rtnType));
semanticScope.setCondition(userStatementNode, Internal.class);
if ("execute".equals(functionName)) {
if (ScriptClassInfo.MAIN_METHOD.equals(functionName)) {
decorateWithCastForReturn(userStatementNode, userExpressionNode, semanticScope,
semanticScope.getScriptScope().getScriptClassInfo());
} else {
Expand Down Expand Up @@ -152,7 +171,7 @@ public void visitReturn(SReturn userReturnNode, SemanticScope semanticScope) {
semanticScope.putDecoration(userValueNode, new TargetType(semanticScope.getReturnType()));
semanticScope.setCondition(userValueNode, Internal.class);
checkedVisit(userValueNode, semanticScope);
if ("execute".equals(functionName)) {
if (ScriptClassInfo.MAIN_METHOD.equals(functionName)) {
decorateWithCastForReturn(userValueNode, userReturnNode, semanticScope,
semanticScope.getScriptScope().getScriptClassInfo());
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class PainlessSemanticHeaderPhase extends DefaultSemanticHeaderPhase {
public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {
String functionName = userFunctionNode.getFunctionName();

if ("execute".equals(functionName)) {
if (ScriptClassInfo.MAIN_METHOD.equals(functionName)) {
ScriptClassInfo scriptClassInfo = scriptScope.getScriptClassInfo();

FunctionTable functionTable = scriptScope.getFunctionTable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {
// to convert get methods into local variables for those
// that are used and adds additional sandboxing by wrapping
// the main "execute" block with several exceptions.
if ("execute".equals(functionName)) {
if (ScriptClassInfo.MAIN_METHOD.equals(functionName)) {
ScriptClassInfo scriptClassInfo = scriptScope.getScriptClassInfo();
LocalFunction localFunction =
scriptScope.getFunctionTable().getFunction(functionName, scriptClassInfo.getExecuteArguments().size());
Expand Down Expand Up @@ -137,7 +137,7 @@ public void visitFunction(SFunction userFunctionNode, ScriptScope scriptScope) {

FunctionNode irFunctionNode = new FunctionNode(userFunctionNode.getLocation());
irFunctionNode.setBlockNode(irBlockNode);
irFunctionNode.attachDecoration(new IRDName("execute"));
irFunctionNode.attachDecoration(new IRDName(ScriptClassInfo.MAIN_METHOD));
irFunctionNode.attachDecoration(new IRDReturnType(returnType));
irFunctionNode.attachDecoration(new IRDTypeParameters(new ArrayList<>(localFunction.getTypeParameters())));
irFunctionNode.attachDecoration(new IRDParameterNames(new ArrayList<>(parameterNames)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,18 @@ public interface ListShortcut extends Condition {

}

public static class GlobalMember implements Decoration {
private final boolean isLocalMember;

public GlobalMember(boolean isLocalMember) {
this.isLocalMember = isLocalMember;
}

public boolean isLocalMember() {
return isLocalMember;
}
}

public static class ExpressionPainlessCast implements Decoration {

private final PainlessCast expressionPainlessCast;
Expand Down
Loading

0 comments on commit 4084734

Please sign in to comment.