Java tutorial
/* * 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. */ package org.elasticsearch.painless; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.PainlessParser.ExpressionContext; import org.elasticsearch.painless.PainlessParser.PrecedenceContext; import java.util.HashMap; import java.util.Map; /** * Metadata is a wrapper for all the data that is collected by the {@link Analyzer}. Each node in the ANTLR parse tree * will have one of the types of metadata to store information used either in a different node by the analyzer * or by the {@link Writer} during byte code generation. Metadata also contains several objects passed into the * {@link Analyzer} and {@link Writer} used during compilation including the {@link Definition}, the source code, * the root of the ANTLR parse tree, and the {@link CompilerSettings}. */ class Metadata { /** * StatementMetadata is used to store metadata mostly about * control flow for ANTLR nodes related to if/else, do, while, for, etc. */ static class StatementMetadata { /** * The source variable is the ANTLR node used to generate this metadata. */ final ParserRuleContext source; /** * The lastSource variable will be set to true when the final statement from the root ANTLR node is about * to be visited. This is used to determine whether or not the auto-return feature is allowed to be used, * and if a null return value needs to be generated automatically since a return value is always required. */ boolean lastSource = false; /** * The beginLoop variable will be set to true whenever a loop node is initially visited including inner * loops. This will not be propagated down the parse tree afterwards, though. This is used to determine * whether or not inLoop should be set further down the tree. Note that inLoop alone is not enough * information to determine whether we are in the last statement of a loop because we may inside of * multiple loops, so this variable is necessary. */ boolean beginLoop = false; /** * The inLoop variable is set to true when inside a loop. This will be propagated down the parse tree. This * is used to determine whether or not continue and break statements are legal. */ boolean inLoop = false; /** * The lastLoop variable is set to true when the final statement of a loop is reached. This will be * propagated down the parse tree until another loop is reached and then will not be propagated further for * the current loop. This is used to determine whether or not a continue statement is superfluous. */ boolean lastLoop = false; /** * The methodEscape variable is set to true when a statement would cause the method to potentially exit. This * includes return, throw, and continuous loop statements. Note that a catch statement may possibly * reset this to false after a throw statement. This will be propagated up the tree as far as necessary. * This is used by the {@link Writer} to ensure that superfluous statements aren't unnecessarily written * into generated bytecode. */ boolean methodEscape = false; /** * The loopEscape variable is set to true when a loop is going to be exited. This may be caused by a number of * different statements including continue, break, return, etc. This will only be propagated as far as the * loop node. This is used to ensure that in certain case an infinite loop will be caught at * compile-time rather than run-time. */ boolean loopEscape = false; /** * The allLast variable is set whenever a final statement in a block is reached. This includes the end of loop, * if, else, etc. This will be only propagated to the top of the block statement ANTLR node. * This is used to ensure that there are no unreachable statements within the script. */ boolean allLast = false; /** * The anyContinue will be set to true when a continue statement is visited. This will be propagated to the * loop node it's within. This is used to ensure that in certain case an infinite loop will be caught at * compile-time rather than run-time. */ boolean anyContinue = false; /** * The anyBreak will be set to true when a break statement is visited. This will be propagated to the * loop node it's within. This is used to in conjunction with methodEscape to ensure there are no unreachable * statements within the script. */ boolean anyBreak = false; /** * The count variable is used as a rudimentary count of statements within a loop. This will be used in * the {@link Writer} to keep a count of statements that have been executed at run-time to ensure that a loop * will exit if it runs too long. */ int count = 0; /** * The exception variable is used to store the exception type when a throw node is visited. This is used by * the {@link Writer} to write the correct type of exception in the generated byte code. */ Type exception = null; /** * The slot variable is used to store the place on the stack of where a thrown exception will be stored to. * This is used by the {@link Writer}. */ int slot = -1; /** * Constructor. * @param source The associated ANTLR node. */ private StatementMetadata(final ParserRuleContext source) { this.source = source; } } /** * ExpressionMetadata is used to store metadata mostly about constants and casting * for ANTLR nodes related to mathematical operations. */ static class ExpressionMetadata { /** * The source variable is the ANTLR node used to generate this metadata. */ final ParserRuleContext source; /** * The read variable is used to determine whether or not the value of an expression will be read from. * This is set to false when the expression is the left-hand side of an assignment that is not chained or * when a method call is made alone. This will propagate down the tree as far as necessary. * The {@link Writer} uses this to determine when a value may need to be popped from the stack * such as when a method call returns a value that is never read. */ boolean read = true; /** * The statement variable is set true when an expression is a complete meaning that there is some sort * of effect on a variable or a method call is made. This will propagate up the tree as far as necessary. * This prevents statements that have no effect on the output of a script from being executed. */ boolean statement = false; /** * The preConst variable is set to a non-null value when a constant statement is made in a script. This is * used to track the constant value prior to any casts being made on an ANTLR node. */ Object preConst = null; /** * The postConst variable is set to a non-null value when a cast is made on a node where a preConst variable * has already been set when the cast would leave the constant as a non-object value except in the case of a * String. This will be propagated up the tree and used to simplify constants when possible such as making * the value of 2*2 be 4 in the * node, so that the {@link Writer} only has to push a 4 onto the stack. */ Object postConst = null; /** * The isNull variable is set to true when a null constant statement is made in the script. This allows the * {@link Writer} to potentially shortcut certain comparison operations. */ boolean isNull = false; /** * The to variable is used to track what an ANTLR node's value should be cast to. This is set on every ANTLR * node in the tree, and used by the {@link Writer} to make a run-time cast if necessary in the byte code. * This is also used by the {@link Analyzer} to determine if a cast is legal. */ Type to = null; /** * The from variable is used to track what an ANTLR node's value should be cast from. This is set on every * ANTLR node in the tree independent of other nodes. This is used by the {@link Analyzer} to determine if a * cast is legal. */ Type from = null; /** * The explicit variable is set to true when a cast is explicitly made in the script. This tracks whether * or not a cast is a legal up cast. */ boolean explicit = false; /** * The typesafe variable is set to true when a dynamic type is used as part of an expression. This propagates * up the tree to the top of the expression. This allows for implicit up casts throughout the expression and * is used by the {@link Analyzer}. */ boolean typesafe = true; /** * This is set to the combination of the to and from variables at the end of each node visit in the * {@link Analyzer}. This is set on every ANTLR node in the tree independent of other nodes, and is * used by {@link Writer} to make a run-time cast if necessary in the byte code. */ Cast cast = null; /** * Constructor. * @param source The associated ANTLR node. */ private ExpressionMetadata(final ParserRuleContext source) { this.source = source; } } /** * ExternalMetadata is used to store metadata about the overall state of a variable/method chain such as * '(int)x.get(3)' where each piece of that chain is broken into it's indiviual pieces and stored in * {@link ExtNodeMetadata}. */ static class ExternalMetadata { /** * The source variable is the ANTLR node used to generate this metadata. */ final ParserRuleContext source; /** * The read variable is set to true when the value of a variable/method chain is going to be read from. * This is used by the {@link Analyzer} to determine if this variable/method chain will be in a standalone * statement. */ boolean read = false; /** * The storeExpr variable is set to the right-hand side of an assignment in the variable/method chain if * necessary. This is used by the {@link Analyzer} to set the proper metadata for a read versus a write, * and is used by the {@link Writer} to determine if a bytecode operation should be a load or a store. */ ParserRuleContext storeExpr = null; /** * The token variable is set to a constant value of the operator type (+, -, etc.) when a compound assignment * is being visited. This is also used by the increment and decrement operators. This is used by both the * {@link Analyzer} and {@link Writer} to correctly handle the compound assignment. */ int token = 0; /** * The pre variable is set to true when pre-increment or pre-decrement is visited. This is used by both the * {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are * necessary. */ boolean pre = false; /** * The post variable is set to true when post-increment or post-decrement is visited. This is used by both the * {@link Analyzer} and {@link Writer} to correctly handle any reads of the variable/method chain that are * necessary. */ boolean post = false; /** * The scope variable is incremented and decremented when a precedence node is visited as part of a * variable/method chain. This is used by the {@link Analyzer} to determine when the final piece of the * variable/method chain has been reached. */ int scope = 0; /** * The current variable is set to whatever the current type is within the visited node of the variable/method * chain. This changes as the nodes for the variable/method are walked through. This is used by the * {@link Analyzer} to make decisions about whether or not a cast is legal, and what methods are available * for that specific type. */ Type current = null; /** * The statik variable is set to true when a variable/method chain begins with static type. This is used by * the {@link Analyzer} to determine what methods/members are available for that specific type. */ boolean statik = false; /** * The statement variable is set to true when a variable/method chain can be standalone statement. This is * used by the {@link Analyzer} to error out if there a variable/method chain that is not a statement. */ boolean statement = false; /** * The constant variable is set when a String constant is part of the variable/method chain. String is a * special case because methods/members need to be able to be called on a String constant, so this can't be * only as part of {@link ExpressionMetadata}. This is used by the {@link Writer} to write out the String * constant in the byte code. */ Object constant = null; /** * Constructor. * @param source The associated ANTLR node. */ private ExternalMetadata(final ParserRuleContext source) { this.source = source; } } static class ExtNodeMetadata { /** * The parent variable is top-level ANTLR node of the variable/method chain. This is used to retrieve the * ExternalMetadata for the variable/method chain this ExtNodeMetadata is a piece of. */ final ParserRuleContext parent; /** * The source variable is the ANTLR node used to generate this metadata. */ final ParserRuleContext source; /** * The target variable is set to a value based on the type of ANTLR node that is visited. This is used by * {@link Writer} to determine whether a cast, store, load, or method call should be written in byte code * depending on what the target variable is. */ Object target = null; /** * The last variable is set to true when the last ANTLR node of the variable/method chain is visted. This is * used by the {@link Writer} in conjuction with the storeExpr variable to determine whether or not a store * needs to be written as opposed to a load. */ boolean last = false; /** * The type variable is set to the type that a visited node ends with. This is used by both the * {@link Analyzer} and {@link Writer} to make decisions about compound assignments, String constants, and * shortcuts. */ Type type = null; /** * The promote variable is set to the type of a promotion within a compound assignment. Compound assignments * may require promotion between the left-hand side variable and right-hand side value. This is used by the * {@link Writer} to make the correct decision about the byte code operation. */ Type promote = null; /** * The castFrom variable is set during a compound assignment. This is used by the {@link Writer} to * cast the values to the promoted type during a compound assignment. */ Cast castFrom = null; /** * The castTo variable is set during an explicit cast in a variable/method chain or during a compound * assignment. This is used by the {@link Writer} to either do an explicit cast, or cast the values * from the promoted type back to the original type during a compound assignment. */ Cast castTo = null; /** * Constructor. * @param parent The top-level ANTLR node for the variable/method chain. * @param source The associated ANTLR node. */ private ExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) { this.parent = parent; this.source = source; } } /** * A utility method to output consistent error messages. * @param ctx The ANTLR node the error occurred in. * @return The error message with tacked on line number and character position. */ static String error(final ParserRuleContext ctx) { return "Error [" + ctx.getStart().getLine() + ":" + ctx.getStart().getCharPositionInLine() + "]: "; } /** * Acts as both the Painless API and white-list for what types and methods are allowed. */ final Definition definition; /** * The original text of the input script. This is used to write out the source code into * the byte code file for debugging purposes. */ final String source; /** * Toot node of the ANTLR tree for the Painless script. */ final ParserRuleContext root; /** * Used to determine certain compile-time constraints such as whether or not numeric overflow is allowed * and how many statements are allowed before a loop will throw an exception. */ final CompilerSettings settings; /** * Used to determine what slot the input variable is stored in. This is used in the {@link Writer} whenever * the input variable is accessed. */ int inputValueSlot = -1; /** * Used to determine what slot the score variable is stored in. This is used in the {@link Writer} whenever * the score variable is accessed. */ int scoreValueSlot = -1; /** * Used to determine what slot the loopCounter variable is stored in. This is used n the {@link Writer} whenever * the loop variable is accessed. */ int loopCounterSlot = -1; /** * Maps the relevant ANTLR node to its metadata. */ private final Map<ParserRuleContext, StatementMetadata> statementMetadata = new HashMap<>(); /** * Maps the relevant ANTLR node to its metadata. */ private final Map<ParserRuleContext, ExpressionMetadata> expressionMetadata = new HashMap<>(); /** * Maps the relevant ANTLR node to its metadata. */ private final Map<ParserRuleContext, ExternalMetadata> externalMetadata = new HashMap<>(); /** * Maps the relevant ANTLR node to its metadata. */ private final Map<ParserRuleContext, ExtNodeMetadata> extNodeMetadata = new HashMap<>(); /** * Constructor. * @param definition The Painless definition. * @param source The source text for the script. * @param root The root ANTLR node. * @param settings The compile-time settings. */ Metadata(final Definition definition, final String source, final ParserRuleContext root, final CompilerSettings settings) { this.definition = definition; this.source = source; this.root = root; this.settings = settings; } /** * Creates a new StatementMetadata and stores it in the statementMetadata map. * @param source The ANTLR node for this metadata. * @return The new StatementMetadata. */ StatementMetadata createStatementMetadata(final ParserRuleContext source) { final StatementMetadata sourcesmd = new StatementMetadata(source); statementMetadata.put(source, sourcesmd); return sourcesmd; } /** * Retrieves StatementMetadata from the statementMetadata map. * @param source The ANTLR node for this metadata. * @return The retrieved StatementMetadata. */ StatementMetadata getStatementMetadata(final ParserRuleContext source) { final StatementMetadata sourcesmd = statementMetadata.get(source); if (sourcesmd == null) { throw new IllegalStateException(error(source) + "Statement metadata does not exist at" + " the parse node with text [" + source.getText() + "]."); } return sourcesmd; } /** * The ANTLR parse tree is modified in one single case; a parent node needs to check a child node to see if it's * a precedence node, and if so, it must be removed from the tree permanently. Once the ANTLR tree is built, * precedence nodes are no longer necessary to maintain the correct ordering of the tree, so they only * add a level of indirection where complicated decisions about metadata passing would have to be made. This * method removes the need for those decisions. * @param source The child ANTLR node to check for precedence. * @return The updated child ANTLR node. */ ExpressionContext updateExpressionTree(ExpressionContext source) { // Check to see if the ANTLR node is a precedence node. if (source instanceof PrecedenceContext) { final ParserRuleContext parent = source.getParent(); int index = 0; // Mark the index of the source node within the list of child nodes from the parent. for (final ParseTree child : parent.children) { if (child == source) { break; } ++index; } // If there are multiple precedence nodes in a row, remove them all. while (source instanceof PrecedenceContext) { source = ((PrecedenceContext) source).expression(); } // Update the parent node with the child of the precedence node. parent.children.set(index, source); } return source; } /** * Creates a new ExpressionMetadata and stores it in the expressionMetadata map. * @param source The ANTLR node for this metadata. * @return The new ExpressionMetadata. */ ExpressionMetadata createExpressionMetadata(ParserRuleContext source) { final ExpressionMetadata sourceemd = new ExpressionMetadata(source); expressionMetadata.put(source, sourceemd); return sourceemd; } /** * Retrieves ExpressionMetadata from the expressionMetadata map. * @param source The ANTLR node for this metadata. * @return The retrieved ExpressionMetadata. */ ExpressionMetadata getExpressionMetadata(final ParserRuleContext source) { final ExpressionMetadata sourceemd = expressionMetadata.get(source); if (sourceemd == null) { throw new IllegalStateException(error(source) + "Expression metadata does not exist at" + " the parse node with text [" + source.getText() + "]."); } return sourceemd; } /** * Creates a new ExternalMetadata and stores it in the externalMetadata map. * @param source The ANTLR node for this metadata. * @return The new ExternalMetadata. */ ExternalMetadata createExternalMetadata(final ParserRuleContext source) { final ExternalMetadata sourceemd = new ExternalMetadata(source); externalMetadata.put(source, sourceemd); return sourceemd; } /** * Retrieves ExternalMetadata from the externalMetadata map. * @param source The ANTLR node for this metadata. * @return The retrieved ExternalMetadata. */ ExternalMetadata getExternalMetadata(final ParserRuleContext source) { final ExternalMetadata sourceemd = externalMetadata.get(source); if (sourceemd == null) { throw new IllegalStateException(error(source) + "External metadata does not exist at" + " the parse node with text [" + source.getText() + "]."); } return sourceemd; } /** * Creates a new ExtNodeMetadata and stores it in the extNodeMetadata map. * @param source The ANTLR node for this metadata. * @return The new ExtNodeMetadata. */ ExtNodeMetadata createExtNodeMetadata(final ParserRuleContext parent, final ParserRuleContext source) { final ExtNodeMetadata sourceemd = new ExtNodeMetadata(parent, source); extNodeMetadata.put(source, sourceemd); return sourceemd; } /** * Retrieves ExtNodeMetadata from the extNodeMetadata map. * @param source The ANTLR node for this metadata. * @return The retrieved ExtNodeMetadata. */ ExtNodeMetadata getExtNodeMetadata(final ParserRuleContext source) { final ExtNodeMetadata sourceemd = extNodeMetadata.get(source); if (sourceemd == null) { throw new IllegalStateException(error(source) + "External metadata does not exist at" + " the parse node with text [" + source.getText() + "]."); } return sourceemd; } }