diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java index c75e0470e0a02..ab09010d248d4 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java @@ -209,20 +209,18 @@ private static void addFactoryMethod(Map> additionalClasses, Cl ScriptRoot compile(Loader loader, String name, String source, CompilerSettings settings) { ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, null); - ScriptRoot scriptRoot = root.analyze(painlessLookup, settings); + ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root); + root.analyze(scriptRoot); ClassNode classNode = root.writeClass(); + DefBootstrapInjectionPhase.phase(classNode); ScriptInjectionPhase.phase(scriptRoot, classNode); - Map statics = classNode.write(); + byte[] bytes = classNode.write(); try { - Class clazz = loader.defineScript(CLASS_NAME, classNode.getBytes()); - clazz.getField("$NAME").set(null, name); - clazz.getField("$SOURCE").set(null, source); - clazz.getField("$STATEMENTS").set(null, classNode.getStatements()); - clazz.getField("$DEFINITION").set(null, painlessLookup); - - for (Map.Entry statik : statics.entrySet()) { - clazz.getField(statik.getKey()).set(null, statik.getValue()); + Class clazz = loader.defineScript(CLASS_NAME, bytes); + + for (Map.Entry staticConstant : scriptRoot.getStaticConstants().entrySet()) { + clazz.getField(staticConstant.getKey()).set(null, staticConstant.getValue()); } return scriptRoot; @@ -241,11 +239,12 @@ ScriptRoot compile(Loader loader, String name, String source, CompilerSettings s byte[] compile(String name, String source, CompilerSettings settings, Printer debugStream) { ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass); SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, debugStream); - ScriptRoot scriptRoot = root.analyze(painlessLookup, settings); + ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root); + root.analyze(scriptRoot); ClassNode classNode = root.writeClass(); + DefBootstrapInjectionPhase.phase(classNode); ScriptInjectionPhase.phase(scriptRoot, classNode); - classNode.write(); - return classNode.getBytes(); + return classNode.write(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrapInjectionPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrapInjectionPhase.java new file mode 100644 index 0000000000000..603defb7c82d1 --- /dev/null +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrapInjectionPhase.java @@ -0,0 +1,218 @@ +package org.elasticsearch.painless;/* + * 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. + */ + +import org.elasticsearch.painless.ir.BlockNode; +import org.elasticsearch.painless.ir.CallNode; +import org.elasticsearch.painless.ir.CallSubNode; +import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.FieldNode; +import org.elasticsearch.painless.ir.FunctionNode; +import org.elasticsearch.painless.ir.MemberFieldNode; +import org.elasticsearch.painless.ir.ReturnNode; +import org.elasticsearch.painless.ir.StaticNode; +import org.elasticsearch.painless.ir.VariableNode; +import org.elasticsearch.painless.lookup.PainlessLookup; +import org.elasticsearch.painless.lookup.PainlessMethod; +import org.elasticsearch.painless.symbol.FunctionTable; +import org.objectweb.asm.Opcodes; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.invoke.MethodType; +import java.util.Arrays; + +/** + * This injects additional ir nodes required for + * resolving the def type at runtime. This includes injection + * of ir nodes to add a function to call + * {@link DefBootstrap#bootstrap(PainlessLookup, FunctionTable, Lookup, String, MethodType, int, int, Object...)} + * to do the runtime resolution. + */ +public class DefBootstrapInjectionPhase { + + public static void phase(ClassNode classNode) { + injectStaticFields(classNode); + injectDefBootstrapMethod(classNode); + } + + // adds static fields required for def bootstrapping + protected static void injectStaticFields(ClassNode classNode) { + Location internalLocation = new Location("$internal$DefBootstrapInjectionPhase$injectStaticFields", 0); + int modifiers = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; + + FieldNode fieldNode = new FieldNode(); + fieldNode.setLocation(internalLocation); + fieldNode.setModifiers(modifiers); + fieldNode.setFieldType(PainlessLookup.class); + fieldNode.setName("$DEFINITION"); + + classNode.addFieldNode(fieldNode); + + fieldNode = new FieldNode(); + fieldNode.setLocation(internalLocation); + fieldNode.setModifiers(modifiers); + fieldNode.setFieldType(FunctionTable.class); + fieldNode.setName("$FUNCTIONS"); + + classNode.addFieldNode(fieldNode); + } + + // adds the bootstrap method required for dynamic binding for def type resolution + protected static void injectDefBootstrapMethod(ClassNode classNode) { + Location internalLocation = new Location("$internal$DefBootstrapInjectionPhase$injectDefBootstrapMethod", 0); + + try { + FunctionNode functionNode = new FunctionNode(); + functionNode.setLocation(internalLocation); + functionNode.setReturnType(CallSite.class); + functionNode.setName("$bootstrapDef"); + functionNode.getTypeParameters().addAll( + Arrays.asList(Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class)); + functionNode.getParameterNames().addAll( + Arrays.asList("methodHandlesLookup", "name", "type", "initialDepth", "flavor", "args")); + functionNode.setStatic(true); + functionNode.setVarArgs(true); + functionNode.setSynthetic(true); + functionNode.setMaxLoopCounter(0); + + classNode.addFunctionNode(functionNode); + + BlockNode blockNode = new BlockNode(); + blockNode.setLocation(internalLocation); + blockNode.setAllEscape(true); + blockNode.setStatementCount(1); + + functionNode.setBlockNode(blockNode); + + ReturnNode returnNode = new ReturnNode(); + returnNode.setLocation(internalLocation); + + blockNode.addStatementNode(returnNode); + + CallNode callNode = new CallNode(); + callNode.setLocation(internalLocation); + callNode.setExpressionType(CallSite.class); + + returnNode.setExpressionNode(callNode); + + StaticNode staticNode = new StaticNode(); + staticNode.setLocation(internalLocation); + staticNode.setExpressionType(DefBootstrap.class); + + callNode.setLeftNode(staticNode); + + CallSubNode callSubNode = new CallSubNode(); + callSubNode.setLocation(internalLocation); + callSubNode.setExpressionType(CallSite.class); + callSubNode.setMethod(new PainlessMethod( + DefBootstrap.class.getMethod("bootstrap", + PainlessLookup.class, + FunctionTable.class, + Lookup.class, + String.class, + MethodType.class, + int.class, + int.class, + Object[].class), + DefBootstrap.class, + CallSite.class, + Arrays.asList( + PainlessLookup.class, + FunctionTable.class, + Lookup.class, + String.class, + MethodType.class, + int.class, + int.class, + Object[].class), + null, + null, + null + ) + ); + callSubNode.setBox(DefBootstrap.class); + + callNode.setRightNode(callSubNode); + + MemberFieldNode memberFieldNode = new MemberFieldNode(); + memberFieldNode.setLocation(internalLocation); + memberFieldNode.setExpressionType(PainlessLookup.class); + memberFieldNode.setName("$DEFINITION"); + memberFieldNode.setStatic(true); + + callSubNode.addArgumentNode(memberFieldNode); + + memberFieldNode = new MemberFieldNode(); + memberFieldNode.setLocation(internalLocation); + memberFieldNode.setExpressionType(FunctionTable.class); + memberFieldNode.setName("$FUNCTIONS"); + memberFieldNode.setStatic(true); + + callSubNode.addArgumentNode(memberFieldNode); + + VariableNode variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(Lookup.class); + variableNode.setName("methodHandlesLookup"); + + callSubNode.addArgumentNode(variableNode); + + variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(String.class); + variableNode.setName("name"); + + callSubNode.addArgumentNode(variableNode); + + variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(MethodType.class); + variableNode.setName("type"); + + callSubNode.addArgumentNode(variableNode); + + variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(int.class); + variableNode.setName("initialDepth"); + + callSubNode.addArgumentNode(variableNode); + + variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(int.class); + variableNode.setName("flavor"); + + callSubNode.addArgumentNode(variableNode); + + variableNode = new VariableNode(); + variableNode.setLocation(internalLocation); + variableNode.setExpressionType(Object[].class); + variableNode.setName("args"); + + callSubNode.addArgumentNode(variableNode); + } catch (Exception exception) { + throw new RuntimeException(exception); + } + } + + private DefBootstrapInjectionPhase() { + // do nothing + } +} diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInjectionPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInjectionPhase.java index 365d398eee7c6..6fe930bb33fea 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInjectionPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptInjectionPhase.java @@ -24,23 +24,28 @@ import org.elasticsearch.painless.ir.CallSubNode; import org.elasticsearch.painless.ir.CatchNode; import org.elasticsearch.painless.ir.ClassNode; +import org.elasticsearch.painless.ir.ConstantNode; import org.elasticsearch.painless.ir.DeclarationNode; +import org.elasticsearch.painless.ir.FieldNode; import org.elasticsearch.painless.ir.FunctionNode; +import org.elasticsearch.painless.ir.MemberCallNode; +import org.elasticsearch.painless.ir.MemberFieldNode; +import org.elasticsearch.painless.ir.ReturnNode; import org.elasticsearch.painless.ir.StatementNode; import org.elasticsearch.painless.ir.StaticNode; import org.elasticsearch.painless.ir.ThrowNode; import org.elasticsearch.painless.ir.TryNode; -import org.elasticsearch.painless.ir.MemberCallNode; -import org.elasticsearch.painless.ir.MemberFieldNode; import org.elasticsearch.painless.ir.VariableNode; import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.symbol.FunctionTable.LocalFunction; import org.elasticsearch.painless.symbol.ScriptRoot; import org.elasticsearch.script.ScriptException; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.Method; import java.util.Arrays; +import java.util.BitSet; import java.util.Collections; import java.util.Map; @@ -64,12 +69,146 @@ public static void phase(ScriptRoot scriptRoot, ClassNode classNode) { } } - BlockNode blockNode = executeFunctionNode.getBlockNode(); + if (executeFunctionNode == null) { + throw new IllegalStateException("all scripts must have an [execute] method"); + } + + injectStaticFieldsAndGetters(classNode); + injectGetsDeclarations(scriptRoot, executeFunctionNode); + injectNeedsMethods(scriptRoot, classNode); + injectSandboxExceptions(executeFunctionNode); + } + + // adds static fields and getter methods required by PainlessScript for exception handling + protected static void injectStaticFieldsAndGetters(ClassNode classNode) { + Location internalLocation = new Location("$internal$ScriptInjectionPhase$injectStaticFieldsAndGetters", 0); + int modifiers = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC; + + FieldNode fieldNode = new FieldNode(); + fieldNode.setLocation(internalLocation); + fieldNode.setModifiers(modifiers); + fieldNode.setFieldType(String.class); + fieldNode.setName("$NAME"); + + classNode.addFieldNode(fieldNode); + + fieldNode = new FieldNode(); + fieldNode.setLocation(internalLocation); + fieldNode.setModifiers(modifiers); + fieldNode.setFieldType(String.class); + fieldNode.setName("$SOURCE"); + + classNode.addFieldNode(fieldNode); + + fieldNode = new FieldNode(); + fieldNode.setLocation(internalLocation); + fieldNode.setModifiers(modifiers); + fieldNode.setFieldType(BitSet.class); + fieldNode.setName("$STATEMENTS"); + + classNode.addFieldNode(fieldNode); + + FunctionNode functionNode = new FunctionNode(); + functionNode.setLocation(internalLocation); + functionNode.setName("getName"); + functionNode.setReturnType(String.class); + functionNode.setStatic(false); + functionNode.setVarArgs(false); + functionNode.setSynthetic(true); + functionNode.setMaxLoopCounter(0); + + classNode.addFunctionNode(functionNode); + + BlockNode blockNode = new BlockNode(); + blockNode.setLocation(internalLocation); + blockNode.setAllEscape(true); + blockNode.setStatementCount(1); + + functionNode.setBlockNode(blockNode); + + ReturnNode returnNode = new ReturnNode(); + returnNode.setLocation(internalLocation); + + blockNode.addStatementNode(returnNode); + + MemberFieldNode memberFieldNode = new MemberFieldNode(); + memberFieldNode.setLocation(internalLocation); + memberFieldNode.setExpressionType(String.class); + memberFieldNode.setName("$NAME"); + memberFieldNode.setStatic(true); + + returnNode.setExpressionNode(memberFieldNode); + + functionNode = new FunctionNode(); + functionNode.setLocation(internalLocation); + functionNode.setName("getSource"); + functionNode.setReturnType(String.class); + functionNode.setStatic(false); + functionNode.setVarArgs(false); + functionNode.setSynthetic(true); + functionNode.setMaxLoopCounter(0); + + classNode.addFunctionNode(functionNode); + + blockNode = new BlockNode(); + blockNode.setLocation(internalLocation); + blockNode.setAllEscape(true); + blockNode.setStatementCount(1); + + functionNode.setBlockNode(blockNode); - // convert gets methods to a new set of inserted ir nodes as necessary - - // requires the gets method name be modified from "getExample" to "example" - // if a get method variable isn't used it's declaration node is removed from - // the ir tree permanently so there is no frivolous variable slotting + returnNode = new ReturnNode(); + returnNode.setLocation(internalLocation); + + blockNode.addStatementNode(returnNode); + + memberFieldNode = new MemberFieldNode(); + memberFieldNode.setLocation(internalLocation); + memberFieldNode.setExpressionType(String.class); + memberFieldNode.setName("$SOURCE"); + memberFieldNode.setStatic(true); + + returnNode.setExpressionNode(memberFieldNode); + + functionNode = new FunctionNode(); + functionNode.setLocation(internalLocation); + functionNode.setName("getStatements"); + functionNode.setReturnType(BitSet.class); + functionNode.setStatic(false); + functionNode.setVarArgs(false); + functionNode.setSynthetic(true); + functionNode.setMaxLoopCounter(0); + + classNode.addFunctionNode(functionNode); + + blockNode = new BlockNode(); + blockNode.setLocation(internalLocation); + blockNode.setAllEscape(true); + blockNode.setStatementCount(1); + + functionNode.setBlockNode(blockNode); + + returnNode = new ReturnNode(); + returnNode.setLocation(internalLocation); + + blockNode.addStatementNode(returnNode); + + memberFieldNode = new MemberFieldNode(); + memberFieldNode.setLocation(internalLocation); + memberFieldNode.setExpressionType(BitSet.class); + memberFieldNode.setName("$STATEMENTS"); + memberFieldNode.setStatic(true); + + returnNode.setExpressionNode(memberFieldNode); + } + + // convert gets methods to a new set of inserted ir nodes as necessary - + // requires the gets method name be modified from "getExample" to "example" + // if a get method variable isn't used it's declaration node is removed from + // the ir tree permanently so there is no frivolous variable slotting + protected static void injectGetsDeclarations(ScriptRoot scriptRoot, FunctionNode functionNode) { + Location internalLocation = new Location("$internal$ScriptInjectionPhase$injectGetsDeclarations", 0); + BlockNode blockNode = functionNode.getBlockNode(); int statementIndex = 0; while (statementIndex < blockNode.getStatementsNodes().size()) { @@ -87,12 +226,12 @@ public static void phase(ScriptRoot scriptRoot, ClassNode classNode) { if (name.equals(declarationNode.getName())) { if (scriptRoot.getUsedVariables().contains(name)) { - MemberCallNode memberCallNode = new MemberCallNode(); - memberCallNode.setLocation(declarationNode.getLocation()); - memberCallNode.setExpressionType(declarationNode.getDeclarationType()); - memberCallNode.setLocalFunction(new LocalFunction( - getMethod.getName(), returnType, Collections.emptyList(), true, false)); - declarationNode.setExpressionNode(memberCallNode); + MemberCallNode memberCallNode = new MemberCallNode(); + memberCallNode.setLocation(internalLocation); + memberCallNode.setExpressionType(declarationNode.getDeclarationType()); + memberCallNode.setLocalFunction(new LocalFunction( + getMethod.getName(), returnType, Collections.emptyList(), true, false)); + declarationNode.setExpressionNode(memberCallNode); } else { blockNode.getStatementsNodes().remove(statementIndex); isRemoved = true; @@ -109,18 +248,62 @@ public static void phase(ScriptRoot scriptRoot, ClassNode classNode) { ++statementIndex; } } + } + + // injects needs methods as defined by ScriptClassInfo + protected static void injectNeedsMethods(ScriptRoot scriptRoot, ClassNode classNode) { + Location internalLocation = new Location("$internal$ScriptInjectionPhase$injectNeedsMethods", 0); + + for (org.objectweb.asm.commons.Method needsMethod : scriptRoot.getScriptClassInfo().getNeedsMethods()) { + String name = needsMethod.getName(); + name = name.substring(5); + name = Character.toLowerCase(name.charAt(0)) + name.substring(1); + + FunctionNode functionNode = new FunctionNode(); + functionNode.setLocation(internalLocation); + functionNode.setName(needsMethod.getName()); + functionNode.setReturnType(boolean.class); + functionNode.setStatic(false); + functionNode.setVarArgs(false); + functionNode.setSynthetic(true); + functionNode.setMaxLoopCounter(0); + + classNode.addFunctionNode(functionNode); + + BlockNode blockNode = new BlockNode(); + blockNode.setLocation(internalLocation); + blockNode.setAllEscape(true); + blockNode.setStatementCount(1); + + functionNode.setBlockNode(blockNode); + + ReturnNode returnNode = new ReturnNode(); + returnNode.setLocation(internalLocation); + + blockNode.addStatementNode(returnNode); + + ConstantNode constantNode = new ConstantNode(); + constantNode.setLocation(internalLocation); + constantNode.setExpressionType(boolean.class); + constantNode.setConstant(scriptRoot.getUsedVariables().contains(name)); + + returnNode.setExpressionNode(constantNode); + } + } - // decorate the execute method with nodes to wrap the user statements with - // the sandboxed errors as follows: - // } catch (PainlessExplainError e) { - // throw this.convertToScriptException(e, e.getHeaders($DEFINITION)) - // } - // and - // } catch (PainlessError | BootstrapMethodError | OutOfMemoryError | StackOverflowError | Exception e) { - // throw this.convertToScriptException(e, e.getHeaders()) - // } + // decorate the execute method with nodes to wrap the user statements with + // the sandboxed errors as follows: + // } catch (PainlessExplainError e) { + // throw this.convertToScriptException(e, e.getHeaders($DEFINITION)) + // } + // and + // } catch (PainlessError | BootstrapMethodError | OutOfMemoryError | StackOverflowError | Exception e) { + // throw this.convertToScriptException(e, e.getHeaders()) + // } + protected static void injectSandboxExceptions(FunctionNode functionNode) { try { - Location internalLocation = new Location("", 0); + Location internalLocation = new Location("$internal$ScriptInjectionPhase$injectSandboxExceptions", 0); + BlockNode blockNode = functionNode.getBlockNode(); TryNode tryNode = new TryNode(); tryNode.setLocation(internalLocation); @@ -298,7 +481,7 @@ public static void phase(ScriptRoot scriptRoot, ClassNode classNode) { blockNode.setStatementCount(blockNode.getStatementCount()); blockNode.addStatementNode(tryNode); - executeFunctionNode.setBlockNode(blockNode); + functionNode.setBlockNode(blockNode); } catch (Exception exception) { throw new RuntimeException(exception); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java index fd0491977ef12..c3e3b5108af01 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java @@ -19,11 +19,7 @@ package org.elasticsearch.painless; -import org.elasticsearch.painless.api.Augmentation; -import org.elasticsearch.painless.lookup.PainlessLookup; -import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.script.JodaCompatibleZonedDateTime; -import org.elasticsearch.script.ScriptException; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -34,11 +30,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.time.ZonedDateTime; -import java.util.BitSet; import java.util.Collection; -import java.util.Collections; import java.util.Iterator; -import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -52,47 +45,20 @@ public final class WriterConstants { public static final int ASM_VERSION = Opcodes.ASM5; public static final String BASE_INTERFACE_NAME = PainlessScript.class.getName(); public static final Type BASE_INTERFACE_TYPE = Type.getType(PainlessScript.class); - public static final Method CONVERT_TO_SCRIPT_EXCEPTION_METHOD = getAsmMethod(ScriptException.class, "convertToScriptException", - Throwable.class, Map.class); public static final String CLASS_NAME = BASE_INTERFACE_NAME + "$Script"; - public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/')); + public static final Type CLASS_TYPE = Type.getObjectType(CLASS_NAME.replace('.', '/')); public static final String CTOR_METHOD_NAME = ""; public static final Method CLINIT = getAsmMethod(void.class, ""); - public static final String GET_NAME_NAME = "getName"; - public static final Method GET_NAME_METHOD = getAsmMethod(String.class, GET_NAME_NAME); - - public static final String GET_SOURCE_NAME = "getSource"; - public static final Method GET_SOURCE_METHOD = getAsmMethod(String.class, GET_SOURCE_NAME); - - public static final String GET_STATEMENTS_NAME = "getStatements"; - public static final Method GET_STATEMENTS_METHOD = getAsmMethod(BitSet.class, GET_STATEMENTS_NAME); - - // All of these types are caught by the main method and rethrown as ScriptException - public static final Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class); - public static final Type BOOTSTRAP_METHOD_ERROR_TYPE = Type.getType(BootstrapMethodError.class); - public static final Type OUT_OF_MEMORY_ERROR_TYPE = Type.getType(OutOfMemoryError.class); - public static final Type STACK_OVERFLOW_ERROR_TYPE = Type.getType(StackOverflowError.class); - public static final Type EXCEPTION_TYPE = Type.getType(Exception.class); - public static final Type PAINLESS_EXPLAIN_ERROR_TYPE = Type.getType(PainlessExplainError.class); - public static final Method PAINLESS_EXPLAIN_ERROR_GET_HEADERS_METHOD = getAsmMethod(Map.class, "getHeaders", PainlessLookup.class); + public static final Type PAINLESS_ERROR_TYPE = Type.getType(PainlessError.class); public static final Type OBJECT_TYPE = Type.getType(Object.class); - public static final Type BITSET_TYPE = Type.getType(BitSet.class); - - public static final Type DEFINITION_TYPE = Type.getType(PainlessLookup.class); - - public static final Type COLLECTIONS_TYPE = Type.getType(Collections.class); - public static final Method EMPTY_MAP_METHOD = getAsmMethod(Map.class, "emptyMap"); public static final MethodType NEEDS_PARAMETER_METHOD_TYPE = MethodType.methodType(boolean.class); - public static final Type MAP_TYPE = Type.getType(Map.class); - public static final Method MAP_GET = getAsmMethod(Object.class, "get", Object.class); - public static final Type ITERATOR_TYPE = Type.getType(Iterator.class); public static final Method ITERATOR_HASNEXT = getAsmMethod(boolean.class, "hasNext"); public static final Method ITERATOR_NEXT = getAsmMethod(Object.class, "next"); @@ -101,16 +67,10 @@ public final class WriterConstants { public static final Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class); public static final Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class); - public static final Type FUNCTION_TABLE_TYPE = Type.getType(FunctionTable.class); - // TODO: remove this when the transition from Joda to Java datetimes is completed public static final Method JCZDT_TO_ZONEDDATETIME = getAsmMethod(ZonedDateTime.class, "JCZDTToZonedDateTime", JodaCompatibleZonedDateTime.class); - public static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); - - public static final Type AUGMENTATION_TYPE = Type.getType(Augmentation.class); - /** * A Method instance for {@linkplain Pattern}. This isn't available from PainlessLookup because we intentionally don't add it * there so that the script can't create regexes without this syntax. Essentially, our static regex syntax has a monopoly on building @@ -126,9 +86,6 @@ public final class WriterConstants { String.class, MethodType.class, int.class, int.class, Object[].class); static final Handle DEF_BOOTSTRAP_HANDLE = new Handle(Opcodes.H_INVOKESTATIC, CLASS_TYPE.getInternalName(), "$bootstrapDef", DEF_BOOTSTRAP_METHOD.getDescriptor(), false); - public static final Type DEF_BOOTSTRAP_DELEGATE_TYPE = Type.getType(DefBootstrap.class); - public static final Method DEF_BOOTSTRAP_DELEGATE_METHOD = getAsmMethod(CallSite.class, "bootstrap", PainlessLookup.class, - FunctionTable.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class); public static final Type DEF_UTIL_TYPE = Type.getType(Def.class); @@ -172,8 +129,6 @@ public final class WriterConstants { // TODO: remove this when the transition from Joda to Java datetimes is completed public static final Method DEF_TO_ZONEDDATETIME = getAsmMethod(ZonedDateTime.class, "defToZonedDateTime", Object.class); - public static final Type DEF_ARRAY_LENGTH_METHOD_TYPE = Type.getMethodType(Type.INT_TYPE, Type.getType(Object.class)); - /** invokedynamic bootstrap for lambda expression/method references */ public static final MethodType LAMBDA_BOOTSTRAP_TYPE = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java index 6b9805fcd8047..fe2fb0145d888 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/ClassNode.java @@ -37,22 +37,10 @@ import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; -import java.util.HashMap; import java.util.List; -import java.util.Map; import static org.elasticsearch.painless.WriterConstants.BASE_INTERFACE_TYPE; -import static org.elasticsearch.painless.WriterConstants.BITSET_TYPE; import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE; -import static org.elasticsearch.painless.WriterConstants.DEFINITION_TYPE; -import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_METHOD; -import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_DELEGATE_TYPE; -import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_METHOD; -import static org.elasticsearch.painless.WriterConstants.FUNCTION_TABLE_TYPE; -import static org.elasticsearch.painless.WriterConstants.GET_NAME_METHOD; -import static org.elasticsearch.painless.WriterConstants.GET_SOURCE_METHOD; -import static org.elasticsearch.painless.WriterConstants.GET_STATEMENTS_METHOD; -import static org.elasticsearch.painless.WriterConstants.STRING_TYPE; public class ClassNode extends IRNode { @@ -128,18 +116,10 @@ public ScriptRoot getScriptRoot() { /* ---- end node data ---- */ protected Globals globals; - protected byte[] bytes; - public BitSet getStatements() { - return globals.getStatements(); - } - - public byte[] getBytes() { - return bytes; - } - - public Map write() { - this.globals = new Globals(new BitSet(sourceText.length())); + public byte[] write() { + globals = new Globals(new BitSet(sourceText.length())); + scriptRoot.addStaticConstant("$STATEMENTS", globals.getStatements()); // Create the ClassWriter. @@ -154,27 +134,6 @@ public Map write() { ClassVisitor classVisitor = classWriter.getClassVisitor(); classVisitor.visitSource(Location.computeSourceName(name), null); - // Write the a method to bootstrap def calls - MethodWriter bootstrapDef = classWriter.newMethodWriter(Opcodes.ACC_STATIC | Opcodes.ACC_VARARGS, DEF_BOOTSTRAP_METHOD); - bootstrapDef.visitCode(); - bootstrapDef.getStatic(CLASS_TYPE, "$DEFINITION", DEFINITION_TYPE); - bootstrapDef.getStatic(CLASS_TYPE, "$FUNCTIONS", FUNCTION_TABLE_TYPE); - bootstrapDef.loadArgs(); - bootstrapDef.invokeStatic(DEF_BOOTSTRAP_DELEGATE_TYPE, DEF_BOOTSTRAP_DELEGATE_METHOD); - bootstrapDef.returnValue(); - bootstrapDef.endMethod(); - - // Write static variables for name, source and statements used for writing exception messages - classVisitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$NAME", STRING_TYPE.getDescriptor(), null, null).visitEnd(); - classVisitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$SOURCE", STRING_TYPE.getDescriptor(), null, null).visitEnd(); - classVisitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$STATEMENTS", BITSET_TYPE.getDescriptor(), null, null).visitEnd(); - - // Write the static variables used by the method to bootstrap def calls - classVisitor.visitField( - Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$DEFINITION", DEFINITION_TYPE.getDescriptor(), null, null).visitEnd(); - classVisitor.visitField( - Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "$FUNCTIONS", FUNCTION_TABLE_TYPE.getDescriptor(), null, null).visitEnd(); - org.objectweb.asm.commons.Method init; if (scriptClassInfo.getBaseClass().getConstructors().length == 0) { @@ -193,27 +152,6 @@ public Map write() { constructor.returnValue(); constructor.endMethod(); - // Write a method to get static variable source - MethodWriter nameMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, GET_NAME_METHOD); - nameMethod.visitCode(); - nameMethod.getStatic(CLASS_TYPE, "$NAME", STRING_TYPE); - nameMethod.returnValue(); - nameMethod.endMethod(); - - // Write a method to get static variable source - MethodWriter sourceMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, GET_SOURCE_METHOD); - sourceMethod.visitCode(); - sourceMethod.getStatic(CLASS_TYPE, "$SOURCE", STRING_TYPE); - sourceMethod.returnValue(); - sourceMethod.endMethod(); - - // Write a method to get static variable statements - MethodWriter statementsMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, GET_STATEMENTS_METHOD); - statementsMethod.visitCode(); - statementsMethod.getStatic(CLASS_TYPE, "$STATEMENTS", BITSET_TYPE); - statementsMethod.returnValue(); - statementsMethod.endMethod(); - // Write all fields: for (FieldNode fieldNode : fieldNodes) { fieldNode.write(classWriter, null, null, null); @@ -240,32 +178,9 @@ public Map write() { clinit.endMethod(); } - // Write any needsVarName methods for used variables - for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) { - String name = needsMethod.getName(); - name = name.substring(5); - name = Character.toLowerCase(name.charAt(0)) + name.substring(1); - MethodWriter ifaceMethod = classWriter.newMethodWriter(Opcodes.ACC_PUBLIC, needsMethod); - ifaceMethod.visitCode(); - ifaceMethod.push(scriptRoot.getUsedVariables().contains(name)); - ifaceMethod.returnValue(); - ifaceMethod.endMethod(); - } - // End writing the class and store the generated bytes. classVisitor.visitEnd(); - bytes = classWriter.getClassBytes(); - - Map statics = new HashMap<>(); - statics.put("$FUNCTIONS", scriptRoot.getFunctionTable()); - - for (FieldNode fieldNode : fieldNodes) { - if (fieldNode.getInstance() != null) { - statics.put(fieldNode.getName(), fieldNode.getInstance()); - } - } - - return statics; + return classWriter.getClassBytes(); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java index aa9ee2dcbb37d..95e113cbf57ba 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FieldNode.java @@ -33,7 +33,6 @@ public class FieldNode extends IRNode { private int modifiers; private Class fieldType; private String name; - private Object instance; public void setModifiers(int modifiers) { this.modifiers = modifiers; @@ -63,14 +62,6 @@ public String getName() { return name; } - public void setInstance(Object instance) { - this.instance = instance; - } - - public Object getInstance() { - return instance; - } - /* ---- end node data ---- */ @Override diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java index 532c9854256f4..47c944f3b8e27 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/ir/FunctionNode.java @@ -24,7 +24,6 @@ import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.symbol.ScopeTable; import org.elasticsearch.painless.symbol.ScopeTable.Variable; -import org.elasticsearch.painless.symbol.ScriptRoot; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.commons.Method; @@ -48,22 +47,14 @@ public BlockNode getBlockNode() { /* ---- end tree structure, begin node data ---- */ - private ScriptRoot scriptRoot; - private String name; - private Class returnType; - private List> typeParameters = new ArrayList<>(); - private List parameterNames = new ArrayList<>(); - private boolean isStatic; - private boolean isSynthetic; - private int maxLoopCounter; - - public void setScriptRoot(ScriptRoot scriptRoot) { - this.scriptRoot = scriptRoot; - } - - public ScriptRoot getScriptRoot() { - return scriptRoot; - } + protected String name; + Class returnType; + List> typeParameters = new ArrayList<>(); + List parameterNames = new ArrayList<>(); + protected boolean isStatic; + protected boolean hasVarArgs; + protected boolean isSynthetic; + protected int maxLoopCounter; public void setName(String name) { this.name = name; @@ -105,6 +96,14 @@ public boolean isStatic() { return isStatic; } + public void setVarArgs(boolean hasVarArgs) { + this.hasVarArgs = hasVarArgs; + } + + public boolean hasVarArgs() { + return hasVarArgs; + } + public void setSynthetic(boolean isSythetic) { this.isSynthetic = isSythetic; } @@ -133,6 +132,10 @@ protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals scopeTable.defineInternalVariable(Object.class, "this"); } + if (hasVarArgs) { + access |= Opcodes.ACC_VARARGS; + } + if (isSynthetic) { access |= Opcodes.ACC_SYNTHETIC; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java index a0e6d1fd9615e..00f2032d183a1 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECallLocal.java @@ -123,13 +123,14 @@ void analyze(ScriptRoot scriptRoot, Scope scope) { actual = classBinding.returnType; bindingName = scriptRoot.getNextSyntheticName("class_binding"); scriptRoot.getClassNode().addField(new SField(location, - Modifier.PRIVATE, bindingName, classBinding.javaConstructor.getDeclaringClass(), null)); + Modifier.PRIVATE, bindingName, classBinding.javaConstructor.getDeclaringClass())); } else if (instanceBinding != null) { typeParameters = new ArrayList<>(instanceBinding.typeParameters); actual = instanceBinding.returnType; bindingName = scriptRoot.getNextSyntheticName("instance_binding"); scriptRoot.getClassNode().addField(new SField(location, Modifier.STATIC | Modifier.PUBLIC, - bindingName, instanceBinding.targetInstance.getClass(), instanceBinding.targetInstance)); + bindingName, instanceBinding.targetInstance.getClass())); + scriptRoot.addStaticConstant(bindingName, instanceBinding.targetInstance); } else { throw new IllegalStateException("Illegal tree structure."); } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java index 2e93d50df52e2..5dbebcf554af3 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java @@ -211,6 +211,7 @@ LambdaNode write(ClassNode classNode) { functionNode.getTypeParameters().addAll(typeParameters); functionNode.getParameterNames().addAll(parameterNames); functionNode.setStatic(true); + functionNode.setVarArgs(false); functionNode.setSynthetic(true); functionNode.setMaxLoopCounter(maxLoopCounter); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java index a232c66cec68f..5302f17d85907 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENewArrayFunctionRef.java @@ -57,7 +57,6 @@ void analyze(ScriptRoot scriptRoot, Scope scope) { function.generateSignature(scriptRoot.getPainlessLookup()); function.analyze(scriptRoot); scriptRoot.getFunctionTable().addFunction(function.name, function.returnType, function.typeParameters, true, true); - //scriptRoot.getClassNode().addFunction(function); if (expected == null) { ref = null; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java index 1650c9d825c92..a4a1be53006ce 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ERegex.java @@ -76,7 +76,7 @@ void analyze(ScriptRoot scriptRoot, Scope scope) { String name = scriptRoot.getNextSyntheticName("regex"); scriptRoot.getClassNode().addField( - new SField(location, Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE, name, Pattern.class, null)); + new SField(location, Modifier.FINAL | Modifier.STATIC | Modifier.PRIVATE, name, Pattern.class)); constant = new Constant(location, MethodWriter.getType(Pattern.class), name, this::initializeConstant); actual = Pattern.class; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java index a14a9ffa248c2..d946b9b1c69b5 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SClass.java @@ -19,12 +19,10 @@ package org.elasticsearch.painless.node; -import org.elasticsearch.painless.CompilerSettings; import org.elasticsearch.painless.Location; import org.elasticsearch.painless.ScriptClassInfo; import org.elasticsearch.painless.ir.ClassNode; import org.elasticsearch.painless.ir.StatementNode; -import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.symbol.FunctionTable; import org.elasticsearch.painless.symbol.ScriptRoot; import org.objectweb.asm.util.Printer; @@ -67,11 +65,13 @@ void addField(SField field) { fields.add(field); } - public ScriptRoot analyze(PainlessLookup painlessLookup, CompilerSettings settings) { - scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, this); + public ScriptRoot analyze(ScriptRoot scriptRoot) { + this.scriptRoot = scriptRoot; + scriptRoot.addStaticConstant("$NAME", name); + scriptRoot.addStaticConstant("$SOURCE", sourceText); for (SFunction function : functions) { - function.generateSignature(painlessLookup); + function.generateSignature(scriptRoot.getPainlessLookup()); String key = FunctionTable.buildLocalFunctionKey(function.name, function.typeParameters.size()); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java index c0b6f95c14695..944802fb7ac39 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SField.java @@ -31,7 +31,6 @@ public class SField extends ANode { private final int modifiers; private final String name; private final Class type; - private final Object instance; /** * Standard constructor. @@ -39,25 +38,19 @@ public class SField extends ANode { * @param modifiers java modifiers for the field * @param name name of the field * @param type type of the field - * @param instance initial value for the field */ - public SField(Location location, int modifiers, String name, Class type, Object instance) { + public SField(Location location, int modifiers, String name, Class type) { super(location); this.modifiers = modifiers; this.name = name; this.type = type; - this.instance = instance; } public String getName() { return name; } - public Object getInstance() { - return instance; - } - @Override FieldNode write(ClassNode classNode) { FieldNode fieldNode = new FieldNode(); @@ -66,7 +59,6 @@ FieldNode write(ClassNode classNode) { fieldNode.setModifiers(modifiers); fieldNode.setName(name); fieldNode.setFieldType(type); - fieldNode.setInstance(instance); return fieldNode; } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java index f71b7fbb8f538..2ae9ef8d5d78f 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java @@ -69,7 +69,6 @@ public final class SFunction extends ANode { org.objectweb.asm.commons.Method method; - private ScriptRoot scriptRoot; private boolean methodEscape; public SFunction(Location location, String rtnType, String name, @@ -121,7 +120,6 @@ void generateSignature(PainlessLookup painlessLookup) { } void analyze(ScriptRoot scriptRoot) { - this.scriptRoot = scriptRoot; FunctionScope functionScope = newFunctionScope(returnType); for (int index = 0; index < typeParameters.size(); ++index) { @@ -210,12 +208,12 @@ public FunctionNode write(ClassNode classNode) { functionNode.setBlockNode(blockNode); functionNode.setLocation(location); - functionNode.setScriptRoot(scriptRoot); functionNode.setName(name); functionNode.setReturnType(returnType); functionNode.getTypeParameters().addAll(typeParameters); functionNode.getParameterNames().addAll(paramNameStrs); functionNode.setStatic(isStatic); + functionNode.setVarArgs(false); functionNode.setSynthetic(synthetic); functionNode.setMaxLoopCounter(maxLoopCounter); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScriptRoot.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScriptRoot.java index 2ffadc8bad528..71f67d534b9ec 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScriptRoot.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/ScriptRoot.java @@ -25,6 +25,8 @@ import org.elasticsearch.painless.node.SClass; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -44,12 +46,16 @@ public class ScriptRoot { protected boolean deterministic = true; protected Set usedVariables = Collections.emptySet(); + protected Map staticConstants = new HashMap<>(); public ScriptRoot(PainlessLookup painlessLookup, CompilerSettings compilerSettings, ScriptClassInfo scriptClassInfo, SClass classRoot) { this.painlessLookup = Objects.requireNonNull(painlessLookup); this.compilerSettings = Objects.requireNonNull(compilerSettings); this.scriptClassInfo = Objects.requireNonNull(scriptClassInfo); this.classNode = Objects.requireNonNull(classRoot); + + staticConstants.put("$DEFINITION", painlessLookup); + staticConstants.put("$FUNCTIONS", functionTable); } public PainlessLookup getPainlessLookup() { @@ -92,6 +98,14 @@ public void setUsedVariables(Set usedVariables) { } public Set getUsedVariables() { - return usedVariables; + return Collections.unmodifiableSet(usedVariables); + } + + public void addStaticConstant(String name, Object constant) { + staticConstants.put(name, constant); + } + + public Map getStaticConstants() { + return Collections.unmodifiableMap(staticConstants); } }