瀏覽代碼

Move AStatement mutable members into isolated Input/Output objects (#53348)

This is the follow up to #53075, isolating the mutable data on the 
statement nodes as the referenced change did the expression nodes.

This is a step toward the long-term goal of making the "user" trees nodes 
immutable. This change isolates the mutable data for statement nodes in 
the "user" tree during the semantic (analysis) phase by moving the 
mutable data into Input and Output objects. These objects are created 
locally during the semantic phase to share information required for 
semantic checking between parent-child nodes.

Note that for this change, Input and Output are still stored in a mutable 
way on the statement nodes. This will not be the case once the semantic 
(analysis) phase and ir generation (write) phase are combined in a future 
change.

Relates #49869
Jack Conradson 5 年之前
父節點
當前提交
1cdb5b3ecf
共有 22 個文件被更改,包括 337 次插入218 次删除
  1. 1 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java
  2. 72 59
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java
  3. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java
  4. 21 15
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java
  5. 11 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java
  6. 19 13
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java
  7. 11 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java
  8. 8 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java
  9. 7 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
  10. 15 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
  11. 14 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java
  12. 14 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java
  13. 18 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
  14. 6 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java
  15. 14 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java
  16. 25 18
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java
  17. 10 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java
  18. 6 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java
  19. 6 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java
  20. 10 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java
  21. 28 21
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java
  22. 16 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java

+ 1 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java

@@ -91,6 +91,7 @@ public abstract class AExpression extends ANode {
     AExpression prefix;
 
     // TODO: remove placeholders once analysis and write are combined into build
+    // TODO: https://github.com/elastic/elasticsearch/issues/53561
     // This are used to support the transition from a mutable to immutable state.
     // Currently, the IR tree is built during the user tree "write" phase, so
     // these are stored on the node to set during the "semantic" phase and then

+ 72 - 59
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java

@@ -30,66 +30,77 @@ import org.elasticsearch.painless.symbol.ScriptRoot;
  */
 public abstract class AStatement extends ANode {
 
-    /**
-     * Set to true when the final statement in an {@link SClass} is reached.
-     * Used to determine whether or not an auto-return is necessary.
-     */
-    boolean lastSource = false;
-
-    /**
-     * Set to true when a loop begins.  Used by {@link SBlock} to help determine
-     * when the final statement of a loop is reached.
-     */
-    boolean beginLoop = false;
-
-    /**
-     * Set to true when inside a loop.  Used by {@link SBreak} and {@link SContinue}
-     * to determine if a break/continue statement is legal.
-     */
-    boolean inLoop = false;
-
-    /**
-     * Set to true when on the last statement of a loop.  Used by {@link SContinue}
-     * to prevent extraneous continue statements.
-     */
-    boolean lastLoop = false;
-
-    /**
-     * Set to true if a statement would cause the method to exit.  Used to
-     * determine whether or not an auto-return is necessary.
-     */
-    boolean methodEscape = false;
-
-    /**
-     * Set to true if a statement would cause a loop to exit.  Used to
-     * prevent unreachable statements.
-     */
-    boolean loopEscape = false;
-
-    /**
-     * Set to true if all current paths escape from the current {@link SBlock}.
-     * Used during the analysis phase to prevent unreachable statements and
-     * the writing phase to prevent extraneous bytecode gotos from being written.
-     */
-    boolean allEscape = false;
-
-    /**
-     * Set to true if any continue statement occurs in a loop.  Used to prevent
-     * unnecessary infinite loops.
-     */
-    boolean anyContinue = false;
+    public static class Input {
+
+        /**
+         * Set to true when the final statement in an {@link SClass} is reached.
+         * Used to determine whether or not an auto-return is necessary.
+         */
+        boolean lastSource = false;
+
+        /**
+         * Set to true when a loop begins.  Used by {@link SBlock} to help determine
+         * when the final statement of a loop is reached.
+         */
+        boolean beginLoop = false;
+
+        /**
+         * Set to true when inside a loop.  Used by {@link SBreak} and {@link SContinue}
+         * to determine if a break/continue statement is legal.
+         */
+        boolean inLoop = false;
+
+        /**
+         * Set to true when on the last statement of a loop.  Used by {@link SContinue}
+         * to prevent extraneous continue statements.
+         */
+        boolean lastLoop = false;
+    }
 
-    /**
-     * Set to true if any break statement occurs in a loop.  Used to prevent
-     * extraneous loops.
-     */
-    boolean anyBreak = false;
+    public static class Output {
+
+        /**
+         * Set to true if a statement would cause the method to exit.  Used to
+         * determine whether or not an auto-return is necessary.
+         */
+        boolean methodEscape = false;
+
+        /**
+         * Set to true if a statement would cause a loop to exit.  Used to
+         * prevent unreachable statements.
+         */
+        boolean loopEscape = false;
+
+        /**
+         * Set to true if all current paths escape from the current {@link SBlock}.
+         * Used during the analysis phase to prevent unreachable statements and
+         * the writing phase to prevent extraneous bytecode gotos from being written.
+         */
+        boolean allEscape = false;
+
+        /**
+         * Set to true if any continue statement occurs in a loop.  Used to prevent
+         * unnecessary infinite loops.
+         */
+        boolean anyContinue = false;
+
+        /**
+         * Set to true if any break statement occurs in a loop.  Used to prevent
+         * extraneous loops.
+         */
+        boolean anyBreak = false;
+
+        /**
+         * Set to the approximate number of statements in a loop block to prevent
+         * infinite loops during runtime.
+         */
+        int statementCount = 0;
+    }
 
-    /**
-     * Set to the approximate number of statements in a loop block to prevent
-     * infinite loops during runtime.
-     */
-    int statementCount = 0;
+    // TODO: remove placeholders once analysis and write are combined into build
+    // TODO: https://github.com/elastic/elasticsearch/issues/53561
+    Input input;
+    Output output;
 
     /**
      * Standard constructor with location used for error tracking.
@@ -101,7 +112,9 @@ public abstract class AStatement extends ANode {
     /**
      * Checks for errors and collects data for the writing phase.
      */
-    abstract void analyze(ScriptRoot scriptRoot, Scope scope);
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        throw new UnsupportedOperationException();
+    }
 
     /**
      * Writes ASM based on the data collected during the analysis phase.

+ 5 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ELambda.java

@@ -165,17 +165,18 @@ public final class ELambda extends AExpression implements ILambda {
         if (block.statements.isEmpty()) {
             throw createError(new IllegalArgumentException("cannot generate empty lambda"));
         }
-        block.lastSource = true;
-        block.analyze(scriptRoot, lambdaScope);
-        captures = new ArrayList<>(lambdaScope.getCaptures());
+        AStatement.Input blockInput = new AStatement.Input();
+        blockInput.lastSource = true;
+        AStatement.Output blockOutput = block.analyze(scriptRoot, lambdaScope, blockInput);
 
-        if (block.methodEscape == false) {
+        if (blockOutput.methodEscape == false) {
             throw createError(new IllegalArgumentException("not all paths return a value for lambda"));
         }
 
         maxLoopCounter = scriptRoot.getCompilerSettings().getMaxLoopCounter();
 
         // prepend capture list to lambda's arguments
+        captures = new ArrayList<>(lambdaScope.getCaptures());
         this.typeParameters = new ArrayList<>(captures.size() + typeParameters.size());
         parameterNames = new ArrayList<>(captures.size() + paramNameStrs.size());
         for (Variable var : captures) {

+ 21 - 15
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java

@@ -44,7 +44,10 @@ public final class SBlock extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         if (statements == null || statements.isEmpty()) {
             throw createError(new IllegalArgumentException("A block must contain at least one statement."));
         }
@@ -54,22 +57,25 @@ public final class SBlock extends AStatement {
         for (AStatement statement : statements) {
             // Note that we do not need to check after the last statement because
             // there is no statement that can be unreachable after the last.
-            if (allEscape) {
+            if (output.allEscape) {
                 throw createError(new IllegalArgumentException("Unreachable statement."));
             }
 
-            statement.inLoop = inLoop;
-            statement.lastSource = lastSource && statement == last;
-            statement.lastLoop = (beginLoop || lastLoop) && statement == last;
-            statement.analyze(scriptRoot, scope);
-
-            methodEscape = statement.methodEscape;
-            loopEscape = statement.loopEscape;
-            allEscape = statement.allEscape;
-            anyContinue |= statement.anyContinue;
-            anyBreak |= statement.anyBreak;
-            statementCount += statement.statementCount;
+            Input statementInput = new Input();
+            statementInput.inLoop = input.inLoop;
+            statementInput.lastSource = input.lastSource && statement == last;
+            statementInput.lastLoop = (input.beginLoop || input.lastLoop) && statement == last;
+            Output statementOutput = statement.analyze(scriptRoot, scope, statementInput);
+
+            output.methodEscape = statementOutput.methodEscape;
+            output.loopEscape = statementOutput.loopEscape;
+            output.allEscape = statementOutput.allEscape;
+            output.anyContinue |= statementOutput.anyContinue;
+            output.anyBreak |= statementOutput.anyBreak;
+            output.statementCount += statementOutput.statementCount;
         }
+
+        return output;
     }
 
     @Override
@@ -81,8 +87,8 @@ public final class SBlock extends AStatement {
         }
 
         blockNode.setLocation(location);
-        blockNode.setAllEscape(allEscape);
-        blockNode.setStatementCount(statementCount);
+        blockNode.setAllEscape(output.allEscape);
+        blockNode.setStatementCount(output.statementCount);
 
         return blockNode;
     }

+ 11 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java

@@ -35,15 +35,20 @@ public final class SBreak extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
-        if (!inLoop) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
+        if (input.inLoop == false) {
             throw createError(new IllegalArgumentException("Break statement outside of a loop."));
         }
 
-        loopEscape = true;
-        allEscape = true;
-        anyBreak = true;
-        statementCount = 1;
+        output.loopEscape = true;
+        output.allEscape = true;
+        output.anyBreak = true;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 19 - 13
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java

@@ -46,8 +46,11 @@ public final class SCatch extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
-        declaration.analyze(scriptRoot, scope);
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
+        declaration.analyze(scriptRoot, scope, new Input());
 
         Class<?> baseType = baseException.resolveType(scriptRoot.getPainlessLookup()).getType();
         Class<?> type = scope.getVariable(location, declaration.name).getType();
@@ -59,18 +62,21 @@ public final class SCatch extends AStatement {
         }
 
         if (block != null) {
-            block.lastSource = lastSource;
-            block.inLoop = inLoop;
-            block.lastLoop = lastLoop;
-            block.analyze(scriptRoot, scope);
-
-            methodEscape = block.methodEscape;
-            loopEscape = block.loopEscape;
-            allEscape = block.allEscape;
-            anyContinue = block.anyContinue;
-            anyBreak = block.anyBreak;
-            statementCount = block.statementCount;
+            Input blockInput = new Input();
+            blockInput.lastSource = input.lastSource;
+            blockInput.inLoop = input.inLoop;
+            blockInput.lastLoop = input.lastLoop;
+            Output blockOutput = block.analyze(scriptRoot, scope, blockInput);
+
+            output.methodEscape = blockOutput.methodEscape;
+            output.loopEscape = blockOutput.loopEscape;
+            output.allEscape = blockOutput.allEscape;
+            output.anyContinue = blockOutput.anyContinue;
+            output.anyBreak = blockOutput.anyBreak;
+            output.statementCount = blockOutput.statementCount;
         }
+
+        return output;
     }
 
     @Override

+ 11 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java

@@ -35,18 +35,23 @@ public final class SContinue extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
-        if (!inLoop) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
+        if (input.inLoop == false) {
             throw createError(new IllegalArgumentException("Continue statement outside of a loop."));
         }
 
-        if (lastLoop) {
+        if (input.lastLoop) {
             throw createError(new IllegalArgumentException("Extraneous continue statement."));
         }
 
-        allEscape = true;
-        anyContinue = true;
-        statementCount = 1;
+        output.allEscape = true;
+        output.anyContinue = true;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 8 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java

@@ -44,12 +44,17 @@ public final class SDeclBlock extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         for (SDeclaration declaration : declarations) {
-            declaration.analyze(scriptRoot, scope);
+            declaration.analyze(scriptRoot, scope, new Input());
         }
 
-        statementCount = declarations.size();
+        output.statementCount = declarations.size();
+
+        return output;
     }
 
     @Override

+ 7 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java

@@ -48,18 +48,23 @@ public final class SDeclaration extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         DResolvedType resolvedType = type.resolveType(scriptRoot.getPainlessLookup());
         type = resolvedType;
 
         if (expression != null) {
-            Input expressionInput = new Input();
+            AExpression.Input expressionInput = new AExpression.Input();
             expressionInput.expected = resolvedType.getType();
             expression.analyze(scriptRoot, scope, expressionInput);
             expression.cast();
         }
 
         scope.defineVariable(location, resolvedType.getType(), name, false);
+
+        return output;
     }
 
     @Override

+ 15 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java

@@ -45,18 +45,22 @@ public final class SDo extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         scope = scope.newLocalScope();
 
         if (block == null) {
             throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
-        block.beginLoop = true;
-        block.inLoop = true;
-        block.analyze(scriptRoot, scope);
+        Input blockInput = new Input();
+        blockInput.beginLoop = true;
+        blockInput.inLoop = true;
+        Output blockOutput = block.analyze(scriptRoot, scope, blockInput);
 
-        if (block.loopEscape && !block.anyContinue) {
+        if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
             throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
@@ -72,13 +76,15 @@ public final class SDo extends AStatement {
                 throw createError(new IllegalArgumentException("Extraneous do while loop."));
             }
 
-            if (!block.anyBreak) {
-                methodEscape = true;
-                allEscape = true;
+            if (blockOutput.anyBreak == false) {
+                output.methodEscape = true;
+                output.allEscape = true;
             }
         }
 
-        statementCount = 1;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 14 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SEach.java

@@ -53,7 +53,10 @@ public class SEach extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         AExpression.Output expressionOutput = expression.analyze(scriptRoot, scope, new AExpression.Input());
         expression.input.expected = expressionOutput.actual;
         expression.cast();
@@ -76,22 +79,25 @@ public class SEach extends AStatement {
                     "[" + PainlessLookupUtility.typeToCanonicalTypeName(expressionOutput.actual) + "]."));
         }
 
-        sub.analyze(scriptRoot, scope);
+        sub.analyze(scriptRoot, scope, input);
 
         if (block == null) {
             throw createError(new IllegalArgumentException("Extraneous for each loop."));
         }
 
-        block.beginLoop = true;
-        block.inLoop = true;
-        block.analyze(scriptRoot, scope);
-        block.statementCount = Math.max(1, block.statementCount);
+        Input blockInput = new Input();
+        blockInput.beginLoop = true;
+        blockInput.inLoop = true;
+        Output blockOutput = block.analyze(scriptRoot, scope, blockInput);
+        blockOutput.statementCount = Math.max(1, blockOutput.statementCount);
 
-        if (block.loopEscape && !block.anyContinue) {
+        if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
             throw createError(new IllegalArgumentException("Extraneous for loop."));
         }
 
-        statementCount = 1;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 14 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java

@@ -44,36 +44,40 @@ public final class SExpression extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+
         Class<?> rtnType = scope.getReturnType();
         boolean isVoid = rtnType == void.class;
 
         AExpression.Input expressionInput = new AExpression.Input();
-        expressionInput.read = lastSource && !isVoid;
+        expressionInput.read = input.lastSource && !isVoid;
         AExpression.Output expressionOutput = expression.analyze(scriptRoot, scope, expressionInput);
 
-
-        if ((lastSource == false || isVoid) && expressionOutput.statement == false) {
+        if ((input.lastSource == false || isVoid) && expressionOutput.statement == false) {
             throw createError(new IllegalArgumentException("Not a statement."));
         }
 
-        boolean rtn = lastSource && isVoid == false && expressionOutput.actual != void.class;
+        boolean rtn = input.lastSource && isVoid == false && expressionOutput.actual != void.class;
 
         expression.input.expected = rtn ? rtnType : expressionOutput.actual;
         expression.input.internal = rtn;
         expression.cast();
 
-        methodEscape = rtn;
-        loopEscape = rtn;
-        allEscape = rtn;
-        statementCount = 1;
+        output = new Output();
+        output.methodEscape = rtn;
+        output.loopEscape = rtn;
+        output.allEscape = rtn;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override
     StatementNode write(ClassNode classNode) {
         ExpressionNode expressionNode = expression.cast(expression.write(classNode));
 
-        if (methodEscape) {
+        if (output.methodEscape) {
             ReturnNode returnNode = new ReturnNode();
 
             returnNode.setExpressionNode(expressionNode);

+ 18 - 11
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java

@@ -52,12 +52,14 @@ public final class SFor extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+
         scope = scope.newLocalScope();
 
         if (initializer != null) {
             if (initializer instanceof SDeclBlock) {
-                ((SDeclBlock)initializer).analyze(scriptRoot, scope);
+                ((SDeclBlock)initializer).analyze(scriptRoot, scope, new Input());
             } else if (initializer instanceof AExpression) {
                 AExpression initializer = (AExpression)this.initializer;
 
@@ -110,25 +112,30 @@ public final class SFor extends AStatement {
             afterthought.cast();
         }
 
+        output = new Output();
+
         if (block != null) {
-            block.beginLoop = true;
-            block.inLoop = true;
+            Input blockInput = new Input();
+            blockInput.beginLoop = true;
+            blockInput.inLoop = true;
 
-            block.analyze(scriptRoot, scope);
+            Output blockOutput = block.analyze(scriptRoot, scope, blockInput);
 
-            if (block.loopEscape && !block.anyContinue) {
+            if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
                 throw createError(new IllegalArgumentException("Extraneous for loop."));
             }
 
-            if (continuous && !block.anyBreak) {
-                methodEscape = true;
-                allEscape = true;
+            if (continuous && blockOutput.anyBreak == false) {
+                output.methodEscape = true;
+                output.allEscape = true;
             }
 
-            block.statementCount = Math.max(1, block.statementCount);
+            blockOutput.statementCount = Math.max(1, blockOutput.statementCount);
         }
 
-        statementCount = 1;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 6 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFunction.java

@@ -30,6 +30,8 @@ import org.elasticsearch.painless.ir.NullNode;
 import org.elasticsearch.painless.ir.ReturnNode;
 import org.elasticsearch.painless.lookup.PainlessLookup;
 import org.elasticsearch.painless.lookup.PainlessLookupUtility;
+import org.elasticsearch.painless.node.AStatement.Input;
+import org.elasticsearch.painless.node.AStatement.Output;
 import org.elasticsearch.painless.symbol.ScriptRoot;
 
 import java.lang.invoke.MethodType;
@@ -134,9 +136,10 @@ public final class SFunction extends ANode {
             throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "]."));
         }
 
-        block.lastSource = true;
-        block.analyze(scriptRoot, functionScope.newLocalScope());
-        methodEscape = block.methodEscape;
+        Input blockInput = new Input();
+        blockInput.lastSource = true;
+        Output blockOutput = block.analyze(scriptRoot, functionScope.newLocalScope(), blockInput);
+        methodEscape = blockOutput.methodEscape;
 
         if (methodEscape == false && isAutoReturnEnabled == false && returnType != void.class) {
             throw createError(new IllegalArgumentException("not all paths provide a return value " +

+ 14 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java

@@ -43,7 +43,10 @@ public final class SIf extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         AExpression.Input conditionInput = new AExpression.Input();
         conditionInput.expected = boolean.class;
         condition.analyze(scriptRoot, scope, conditionInput);
@@ -57,15 +60,18 @@ public final class SIf extends AStatement {
             throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
-        ifblock.lastSource = lastSource;
-        ifblock.inLoop = inLoop;
-        ifblock.lastLoop = lastLoop;
+        Input ifblockInput = new Input();
+        ifblockInput.lastSource = input.lastSource;
+        ifblockInput.inLoop = input.inLoop;
+        ifblockInput.lastLoop = input.lastLoop;
+
+        Output ifblockOutput = ifblock.analyze(scriptRoot, scope.newLocalScope(), ifblockInput);
 
-        ifblock.analyze(scriptRoot, scope.newLocalScope());
+        output.anyContinue = ifblockOutput.anyContinue;
+        output.anyBreak = ifblockOutput.anyBreak;
+        output.statementCount = ifblockOutput.statementCount;
 
-        anyContinue = ifblock.anyContinue;
-        anyBreak = ifblock.anyBreak;
-        statementCount = ifblock.statementCount;
+        return output;
     }
 
     @Override

+ 25 - 18
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java

@@ -48,7 +48,10 @@ public final class SIfElse extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         AExpression.Input conditionInput = new AExpression.Input();
         conditionInput.expected = boolean.class;
         condition.analyze(scriptRoot, scope, conditionInput);
@@ -62,32 +65,36 @@ public final class SIfElse extends AStatement {
             throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
-        ifblock.lastSource = lastSource;
-        ifblock.inLoop = inLoop;
-        ifblock.lastLoop = lastLoop;
+        Input ifblockInput = new Input();
+        ifblockInput.lastSource = input.lastSource;
+        ifblockInput.inLoop = input.inLoop;
+        ifblockInput.lastLoop = input.lastLoop;
 
-        ifblock.analyze(scriptRoot, scope.newLocalScope());
+        Output ifblockOutput = ifblock.analyze(scriptRoot, scope.newLocalScope(), ifblockInput);
 
-        anyContinue = ifblock.anyContinue;
-        anyBreak = ifblock.anyBreak;
-        statementCount = ifblock.statementCount;
+        output.anyContinue = ifblockOutput.anyContinue;
+        output.anyBreak = ifblockOutput.anyBreak;
+        output.statementCount = ifblockOutput.statementCount;
 
         if (elseblock == null) {
             throw createError(new IllegalArgumentException("Extraneous else statement."));
         }
 
-        elseblock.lastSource = lastSource;
-        elseblock.inLoop = inLoop;
-        elseblock.lastLoop = lastLoop;
+        Input elseblockInput = new Input();
+        elseblockInput.lastSource = input.lastSource;
+        elseblockInput.inLoop = input.inLoop;
+        elseblockInput.lastLoop = input.lastLoop;
+
+        Output elseblockOutput = elseblock.analyze(scriptRoot, scope.newLocalScope(), elseblockInput);
 
-        elseblock.analyze(scriptRoot, scope.newLocalScope());
+        output.methodEscape = ifblockOutput.methodEscape && elseblockOutput.methodEscape;
+        output.loopEscape = ifblockOutput.loopEscape && elseblockOutput.loopEscape;
+        output.allEscape = ifblockOutput.allEscape && elseblockOutput.allEscape;
+        output.anyContinue |= elseblockOutput.anyContinue;
+        output.anyBreak |= elseblockOutput.anyBreak;
+        output.statementCount = Math.max(ifblockOutput.statementCount, elseblockOutput.statementCount);
 
-        methodEscape = ifblock.methodEscape && elseblock.methodEscape;
-        loopEscape = ifblock.loopEscape && elseblock.loopEscape;
-        allEscape = ifblock.allEscape && elseblock.allEscape;
-        anyContinue |= elseblock.anyContinue;
-        anyBreak |= elseblock.anyBreak;
-        statementCount = Math.max(ifblock.statementCount, elseblock.statementCount);
+        return output;
     }
 
     @Override

+ 10 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java

@@ -40,7 +40,10 @@ public final class SReturn extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         if (expression == null) {
             if (scope.getReturnType() != void.class) {
                 throw location.createError(new ClassCastException("Cannot cast from " +
@@ -55,11 +58,13 @@ public final class SReturn extends AStatement {
             expression.cast();
         }
 
-        methodEscape = true;
-        loopEscape = true;
-        allEscape = true;
+        output.methodEscape = true;
+        output.loopEscape = true;
+        output.allEscape = true;
+
+        output.statementCount = 1;
 
-        statementCount = 1;
+        return output;
     }
 
     @Override

+ 6 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachArray.java

@@ -52,13 +52,18 @@ final class SSubEachArray extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         // We must store the array and index as variables for securing slots on the stack, and
         // also add the location offset to make the names unique in case of nested for each loops.
         array = scope.defineInternalVariable(location, expression.output.actual, "array" + location.getOffset(), true);
         index = scope.defineInternalVariable(location, int.class, "index" + location.getOffset(), true);
         indexed = expression.output.actual.getComponentType();
         cast = AnalyzerCaster.getLegalCast(location, indexed, variable.getType(), true, true);
+
+        return output;
     }
 
     @Override

+ 6 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSubEachIterable.java

@@ -57,7 +57,10 @@ final class SSubEachIterable extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         // We must store the iterator as a variable for securing a slot on the stack, and
         // also add the location offset to make the name unique in case of nested for each loops.
         iterator = scope.defineInternalVariable(location, Iterator.class, "itr" + location.getOffset(), true);
@@ -74,6 +77,8 @@ final class SSubEachIterable extends AStatement {
         }
 
         cast = AnalyzerCaster.getLegalCast(location, def.class, variable.getType(), true, true);
+
+        return output;
     }
 
     @Override

+ 10 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java

@@ -41,16 +41,21 @@ public final class SThrow extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         AExpression.Input expressionInput = new AExpression.Input();
         expressionInput.expected = Exception.class;
         expression.analyze(scriptRoot, scope, expressionInput);
         expression.cast();
 
-        methodEscape = true;
-        loopEscape = true;
-        allEscape = true;
-        statementCount = 1;
+        output.methodEscape = true;
+        output.loopEscape = true;
+        output.allEscape = true;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override

+ 28 - 21
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java

@@ -46,42 +46,49 @@ public final class STry extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         if (block == null) {
             throw createError(new IllegalArgumentException("Extraneous try statement."));
         }
 
-        block.lastSource = lastSource;
-        block.inLoop = inLoop;
-        block.lastLoop = lastLoop;
+        Input blockInput = new Input();
+        blockInput.lastSource = input.lastSource;
+        blockInput.inLoop = input.inLoop;
+        blockInput.lastLoop = input.lastLoop;
 
-        block.analyze(scriptRoot, scope.newLocalScope());
+        Output blockOutput = block.analyze(scriptRoot, scope.newLocalScope(), blockInput);
 
-        methodEscape = block.methodEscape;
-        loopEscape = block.loopEscape;
-        allEscape = block.allEscape;
-        anyContinue = block.anyContinue;
-        anyBreak = block.anyBreak;
+        output.methodEscape = blockOutput.methodEscape;
+        output.loopEscape = blockOutput.loopEscape;
+        output.allEscape = blockOutput.allEscape;
+        output.anyContinue = blockOutput.anyContinue;
+        output.anyBreak = blockOutput.anyBreak;
 
         int statementCount = 0;
 
         for (SCatch catc : catches) {
-            catc.lastSource = lastSource;
-            catc.inLoop = inLoop;
-            catc.lastLoop = lastLoop;
+            Input catchInput = new Input();
+            catchInput.lastSource = input.lastSource;
+            catchInput.inLoop = input.inLoop;
+            catchInput.lastLoop = input.lastLoop;
 
-            catc.analyze(scriptRoot, scope.newLocalScope());
+            Output catchOutput = catc.analyze(scriptRoot, scope.newLocalScope(), catchInput);
 
-            methodEscape &= catc.methodEscape;
-            loopEscape &= catc.loopEscape;
-            allEscape &= catc.allEscape;
-            anyContinue |= catc.anyContinue;
-            anyBreak |= catc.anyBreak;
+            output.methodEscape &= catchOutput.methodEscape;
+            output.loopEscape &= catchOutput.loopEscape;
+            output.allEscape &= catchOutput.allEscape;
+            output.anyContinue |= catchOutput.anyContinue;
+            output.anyBreak |= catchOutput.anyBreak;
 
-            statementCount = Math.max(statementCount, catc.statementCount);
+            statementCount = Math.max(statementCount, catchOutput.statementCount);
         }
 
-        this.statementCount = block.statementCount + statementCount;
+        output.statementCount = blockOutput.statementCount + statementCount;
+
+        return output;
     }
 
     @Override

+ 16 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java

@@ -45,7 +45,10 @@ public final class SWhile extends AStatement {
     }
 
     @Override
-    void analyze(ScriptRoot scriptRoot, Scope scope) {
+    Output analyze(ScriptRoot scriptRoot, Scope scope, Input input) {
+        this.input = input;
+        output = new Output();
+
         scope = scope.newLocalScope();
 
         AExpression.Input conditionInput = new AExpression.Input();
@@ -66,24 +69,27 @@ public final class SWhile extends AStatement {
         }
 
         if (block != null) {
-            block.beginLoop = true;
-            block.inLoop = true;
+            Input blockInput = new Input();
+            blockInput.beginLoop = true;
+            blockInput.inLoop = true;
 
-            block.analyze(scriptRoot, scope);
+            Output blockOutput = block.analyze(scriptRoot, scope, blockInput);
 
-            if (block.loopEscape && !block.anyContinue) {
+            if (blockOutput.loopEscape && blockOutput.anyContinue == false) {
                 throw createError(new IllegalArgumentException("Extraneous while loop."));
             }
 
-            if (continuous && !block.anyBreak) {
-                methodEscape = true;
-                allEscape = true;
+            if (continuous && blockOutput.anyBreak == false) {
+                output.methodEscape = true;
+                output.allEscape = true;
             }
 
-            block.statementCount = Math.max(1, block.statementCount);
+            blockOutput.statementCount = Math.max(1, blockOutput.statementCount);
         }
 
-        statementCount = 1;
+        output.statementCount = 1;
+
+        return output;
     }
 
     @Override