1
0
Эх сурвалжийг харах

Merge pull request #18711 from rmuir/painless_compile_exceptions

Improve painless compile-time exceptions
Robert Muir 9 жил өмнө
parent
commit
ad118eb1e5
72 өөрчлөгдсөн 790 нэмэгдсэн , 630 устгасан
  1. 8 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java
  2. 2 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java
  3. 5 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java
  4. 111 0
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Location.java
  5. 10 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java
  6. 62 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java
  7. 2 1
      modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptImpl.java
  8. 26 24
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Variables.java
  9. 1 44
      modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java
  10. 6 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ErrorHandlingLexer.java
  11. 16 18
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ParserErrorStrategy.java
  12. 79 84
      modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java
  13. 12 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java
  14. 3 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java
  15. 8 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java
  16. 3 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java
  17. 53 44
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java
  18. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java
  19. 4 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java
  20. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ECast.java
  21. 19 19
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EChain.java
  22. 28 27
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java
  23. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConditional.java
  24. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java
  25. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java
  26. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EExplicit.java
  27. 6 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java
  28. 4 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java
  29. 10 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java
  30. 13 12
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java
  31. 8 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java
  32. 10 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java
  33. 10 9
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCall.java
  34. 8 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java
  35. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefArray.java
  36. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefCall.java
  37. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LDefField.java
  38. 14 13
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java
  39. 9 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java
  40. 9 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java
  41. 9 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java
  42. 11 10
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java
  43. 9 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java
  44. 8 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java
  45. 7 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java
  46. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LVariable.java
  47. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBlock.java
  48. 4 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java
  49. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java
  50. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SContinue.java
  51. 3 2
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclBlock.java
  52. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java
  53. 8 7
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java
  54. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SExpression.java
  55. 12 11
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SFor.java
  56. 6 5
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java
  57. 7 6
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java
  58. 4 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java
  59. 6 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java
  60. 4 3
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java
  61. 5 4
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/STry.java
  62. 9 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java
  63. 8 8
      modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java
  64. 8 12
      modules/lang-painless/src/test/java/org/elasticsearch/painless/ConditionalTests.java
  65. 2 2
      modules/lang-painless/src/test/java/org/elasticsearch/painless/DivisionTests.java
  66. 1 1
      modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java
  67. 1 1
      modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java
  68. 4 10
      modules/lang-painless/src/test/java/org/elasticsearch/painless/RemainderTests.java
  69. 8 8
      modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java
  70. 4 4
      modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java
  71. 2 2
      modules/lang-painless/src/test/java/org/elasticsearch/painless/antlr/ParserTests.java
  72. 1 1
      modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/16_update2.yaml

+ 8 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/AnalyzerCaster.java

@@ -22,6 +22,7 @@ package org.elasticsearch.painless;
 import org.elasticsearch.painless.Definition.Cast;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.node.ANode;
 
 /**
  * Used during the analysis phase to collect legal type casts and promotions
@@ -29,7 +30,7 @@ import org.elasticsearch.painless.Definition.Type;
  */
 public final class AnalyzerCaster {
 
-    public static Cast getLegalCast(String location, Type actual, Type expected, boolean explicit, boolean internal) {
+    public static Cast getLegalCast(Location location, Type actual, Type expected, boolean explicit, boolean internal) {
         if (actual.equals(expected)) {
             return null;
         }
@@ -653,11 +654,11 @@ public final class AnalyzerCaster {
             explicit && actual.clazz.isAssignableFrom(expected.clazz)) {
             return new Cast(actual, expected, explicit);
         } else {
-            throw new ClassCastException("Error" + location + ": Cannot cast from [" + actual.name + "] to [" + expected.name + "].");
+            throw location.createError(new ClassCastException("Cannot cast from [" + actual.name + "] to [" + expected.name + "]."));
         }
     }
 
-    public static Object constCast(final String location, final Object constant, final Cast cast) {
+    public static Object constCast(Location location, final Object constant, final Cast cast) {
         final Sort fsort = cast.from.sort;
         final Sort tsort = cast.to.sort;
 
@@ -685,12 +686,12 @@ public final class AnalyzerCaster {
                 case FLOAT:  return number.floatValue();
                 case DOUBLE: return number.doubleValue();
                 default:
-                    throw new IllegalStateException("Error" + location + ": Cannot cast from " +
-                        "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "].");
+                    throw location.createError(new IllegalStateException("Cannot cast from " +
+                        "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "]."));
             }
         } else {
-            throw new IllegalStateException("Error" + location + ": Cannot cast from " +
-                "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "].");
+            throw location.createError(new IllegalStateException("Cannot cast from " +
+                "[" + cast.from.clazz.getCanonicalName() + "] to [" + cast.to.clazz.getCanonicalName() + "]."));
         }
     }
 

+ 2 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

@@ -101,7 +101,7 @@ final class Compiler {
         }
 
         Reserved reserved = new Reserved();
-        SSource root = Walker.buildPainlessTree(source, reserved, settings);
+        SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
         Variables variables = Analyzer.analyze(reserved, root);
         BitSet expressions = new BitSet(source.length());
 
@@ -132,7 +132,7 @@ final class Compiler {
         }
 
         Reserved reserved = new Reserved();
-        SSource root = Walker.buildPainlessTree(source, reserved, settings);
+        SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
         Variables variables = Analyzer.analyze(reserved, root);
 
         return Writer.write(settings, name, source, variables, root, new BitSet(source.length()));

+ 5 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/Definition.java

@@ -164,6 +164,11 @@ public final class Definition {
 
             return result;
         }
+
+        @Override
+        public String toString() {
+            return name;
+        }
     }
 
     public static final class Constructor {

+ 111 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/Location.java

@@ -0,0 +1,111 @@
+/*
+ * 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 java.util.Objects;
+
+/**
+ * Represents a location in script code (name of script + character offset)
+ */
+public final class Location {
+    private final String sourceName;
+    private final int offset;
+    
+    /**
+     * Create a new Location 
+     * @param sourceName script's name
+     * @param offset character offset of script element
+     */
+    public Location(String sourceName, int offset) {
+        this.sourceName = Objects.requireNonNull(sourceName);
+        this.offset = offset;
+    }
+    
+    /**
+     * Return the script's name
+     */
+    public String getSourceName() {
+        return sourceName;
+    }
+
+    /**
+     * Return the character offset
+     */
+    public int getOffset() {
+        return offset;
+    }
+
+    /**
+     * Augments an exception with this location's information.
+     */
+    public RuntimeException createError(RuntimeException exception) {
+        StackTraceElement element = new StackTraceElement(WriterConstants.CLASS_NAME, "compile", sourceName, offset + 1);
+        StackTraceElement[] oldStack = exception.getStackTrace();
+        StackTraceElement[] newStack = new StackTraceElement[oldStack.length + 1];
+        System.arraycopy(oldStack, 0, newStack, 1, oldStack.length);
+        newStack[0] = element;
+        exception.setStackTrace(newStack);
+        assert exception.getStackTrace().length == newStack.length : "non-writeable stacktrace for exception: " + exception.getClass();
+        return exception;
+    }
+
+    // This maximum length is theoretically 65535 bytes, but as it's CESU-8 encoded we dont know how large it is in bytes, so be safe
+    private static final int MAX_NAME_LENGTH = 256;
+    
+    /** Computes the file name (mostly important for stacktraces) */
+    public static String computeSourceName(String scriptName, String source) {
+        StringBuilder fileName = new StringBuilder();
+        if (scriptName.equals(PainlessScriptEngineService.INLINE_NAME)) {
+            // its an anonymous script, include at least a portion of the source to help identify which one it is
+            // but don't create stacktraces with filenames that contain newlines or huge names.
+
+            // truncate to the first newline
+            int limit = source.indexOf('\n');
+            if (limit >= 0) {
+                int limit2 = source.indexOf('\r');
+                if (limit2 >= 0) {
+                    limit = Math.min(limit, limit2);
+                }
+            } else {
+                limit = source.length();
+            }
+
+            // truncate to our limit
+            limit = Math.min(limit, MAX_NAME_LENGTH);
+            fileName.append(source, 0, limit);
+
+            // if we truncated, make it obvious
+            if (limit != source.length()) {
+                fileName.append(" ...");
+            }
+            fileName.append(" @ <inline script>");
+        } else {
+            // its a named script, just use the name
+            // but don't trust this has a reasonable length!
+            if (scriptName.length() > MAX_NAME_LENGTH) {
+                fileName.append(scriptName, 0, MAX_NAME_LENGTH);
+                fileName.append(" ...");
+            } else {
+                fileName.append(scriptName);
+            }
+        }
+        return fileName.toString();
+    }
+}

+ 10 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/MethodWriter.java

@@ -114,7 +114,8 @@ public final class MethodWriter extends GeneratorAdapter {
      * <p>
      * This is invoked for each statement boundary (leaf {@code S*} nodes).
      */
-    public void writeStatementOffset(int offset) {
+    public void writeStatementOffset(Location location) {
+        int offset = location.getOffset();
         // ensure we don't have duplicate stuff going in here. can catch bugs
         // (e.g. nodes get assigned wrong offsets by antlr walker)
         assert statements.get(offset) == false;
@@ -126,16 +127,16 @@ public final class MethodWriter extends GeneratorAdapter {
      * <p>
      * This is invoked before instructions that can hit exceptions.
      */
-    public void writeDebugInfo(int offset) {
+    public void writeDebugInfo(Location location) {
         // TODO: maybe track these in bitsets too? this is trickier...
         Label label = new Label();
         visitLabel(label);
-        visitLineNumber(offset + 1, label);
+        visitLineNumber(location.getOffset() + 1, label);
     }
 
-    public void writeLoopCounter(int slot, int count, int offset) {
+    public void writeLoopCounter(int slot, int count, Location location) {
         if (slot > -1) {
-            writeDebugInfo(offset);
+            writeDebugInfo(location);
             final Label end = new Label();
 
             iinc(slot, -count);
@@ -281,14 +282,14 @@ public final class MethodWriter extends GeneratorAdapter {
         }
     }
 
-    public void writeBinaryInstruction(final String location, final Type type, final Operation operation) {
+    public void writeBinaryInstruction(Location location, Type type, Operation operation) {
         final Sort sort = type.sort;
 
         if ((sort == Sort.FLOAT || sort == Sort.DOUBLE) &&
                 (operation == Operation.LSH || operation == Operation.USH ||
                 operation == Operation.RSH || operation == Operation.BWAND ||
                 operation == Operation.XOR || operation == Operation.BWOR)) {
-            throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
+            throw location.createError(new IllegalStateException("Illegal tree structure."));
         }
 
         if (sort == Sort.DEF) {
@@ -305,7 +306,7 @@ public final class MethodWriter extends GeneratorAdapter {
                 case XOR:   invokeStatic(DEF_UTIL_TYPE, DEF_XOR_CALL); break;
                 case BWOR:  invokeStatic(DEF_UTIL_TYPE, DEF_OR_CALL);  break;
                 default:
-                    throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
+                    throw location.createError(new IllegalStateException("Illegal tree structure."));
             }
         } else {
             switch (operation) {
@@ -321,7 +322,7 @@ public final class MethodWriter extends GeneratorAdapter {
                 case XOR:   math(GeneratorAdapter.XOR,  type.type); break;
                 case BWOR:  math(GeneratorAdapter.OR,   type.type); break;
                 default:
-                    throw new IllegalStateException("Error " + location + ": Illegal tree structure.");
+                    throw location.createError(new IllegalStateException("Illegal tree structure."));
             }
         }
     }

+ 62 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngineService.java

@@ -29,6 +29,7 @@ import org.elasticsearch.script.CompiledScript;
 import org.elasticsearch.script.ExecutableScript;
 import org.elasticsearch.script.LeafSearchScript;
 import org.elasticsearch.script.ScriptEngineService;
+import org.elasticsearch.script.ScriptException;
 import org.elasticsearch.script.SearchScript;
 import org.elasticsearch.search.lookup.SearchLookup;
 
@@ -38,7 +39,10 @@ import java.security.AccessController;
 import java.security.Permissions;
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -147,13 +151,17 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
             }
         });
 
-        // Drop all permissions to actually compile the code itself.
-        return AccessController.doPrivileged(new PrivilegedAction<Executable>() {
-            @Override
-            public Executable run() {
-                return Compiler.compile(loader, scriptName == null ? INLINE_NAME : scriptName, scriptSource, compilerSettings);
-            }
-        }, COMPILATION_CONTEXT);
+        try {
+            // Drop all permissions to actually compile the code itself.
+            return AccessController.doPrivileged(new PrivilegedAction<Executable>() {
+                @Override
+                public Executable run() {
+                    return Compiler.compile(loader, scriptName == null ? INLINE_NAME : scriptName, scriptSource, compilerSettings);
+                }
+            }, COMPILATION_CONTEXT);
+        } catch (Exception e) {
+            throw convertToScriptException(scriptName == null ? scriptSource : scriptName, scriptSource, e);
+        }
     }
 
     /**
@@ -213,4 +221,51 @@ public final class PainlessScriptEngineService extends AbstractComponent impleme
     public void close() {
         // Nothing to do.
     }
+    
+    private ScriptException convertToScriptException(String scriptName, String scriptSource, Throwable t) {
+        // create a script stack: this is just the script portion
+        List<String> scriptStack = new ArrayList<>();
+        for (StackTraceElement element : t.getStackTrace()) {
+            if (WriterConstants.CLASS_NAME.equals(element.getClassName())) {
+                // found the script portion
+                int offset = element.getLineNumber();
+                if (offset == -1) {
+                    scriptStack.add("<<< unknown portion of script >>>");
+                } else {
+                    offset--; // offset is 1 based, line numbers must be!
+                    int startOffset = getPreviousStatement(scriptSource, offset);
+                    int endOffset = getNextStatement(scriptSource, offset);
+                    StringBuilder snippet = new StringBuilder();
+                    if (startOffset > 0) {
+                        snippet.append("... ");
+                    }
+                    snippet.append(scriptSource.substring(startOffset, endOffset));
+                    if (endOffset < scriptSource.length()) {
+                        snippet.append(" ...");
+                    }
+                    scriptStack.add(snippet.toString());
+                    StringBuilder pointer = new StringBuilder();
+                    if (startOffset > 0) {
+                        pointer.append("    ");
+                    }
+                    for (int i = startOffset; i < offset; i++) {
+                        pointer.append(' ');
+                    }
+                    pointer.append("^---- HERE");
+                    scriptStack.add(pointer.toString());
+                }
+                break;
+            }
+        }
+        throw new ScriptException("compile error", t, scriptStack, scriptSource, PainlessScriptEngineService.NAME);
+    }
+    
+    // very simple heuristic: +/- 25 chars. can be improved later.
+    private int getPreviousStatement(String scriptSource, int offset) {
+        return Math.max(0, offset - 25);
+    }
+    
+    private int getNextStatement(String scriptSource, int offset) {
+        return Math.min(scriptSource.length(), offset + 25);
+    }
 }

+ 2 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/ScriptImpl.java

@@ -173,7 +173,8 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
     /** returns true for methods that are part of the runtime */
     private static boolean shouldFilter(StackTraceElement element) {
         return element.getClassName().startsWith("org.elasticsearch.painless.") ||
-               element.getClassName().startsWith("java.lang.invoke.");
+               element.getClassName().startsWith("java.lang.invoke.") ||
+               element.getClassName().startsWith("sun.invoke.");
     }
 
     /**

+ 26 - 24
modules/lang-painless/src/main/java/org/elasticsearch/painless/Variables.java

@@ -69,7 +69,7 @@ public final class Variables {
     }
 
     public static final class Variable {
-        public final String location;
+        public final Location location;
         public final String name;
         public final Type type;
         public final int slot;
@@ -77,7 +77,7 @@ public final class Variables {
 
         public boolean read = false;
 
-        private Variable(String location, String name, Type type, int slot, boolean readonly) {
+        private Variable(Location location, String name, Type type, int slot, boolean readonly) {
             this.location = location;
             this.name = name;
             this.type = type;
@@ -88,6 +88,7 @@ public final class Variables {
 
     final Reserved reserved;
 
+    // TODO: this datastructure runs in linear time for nearly all operations. use linkedhashset instead?
     private final Deque<Integer> scopes = new ArrayDeque<>();
     private final Deque<Variable> variables = new ArrayDeque<>();
 
@@ -99,35 +100,35 @@ public final class Variables {
         // Method variables.
 
         // This reference.  Internal use only.
-        addVariable("[" + Reserved.THIS + "]", Definition.getType("Object"), Reserved.THIS, true, true);
+        addVariable(null, Definition.getType("Object"), Reserved.THIS, true, true);
 
         // Input map of variables passed to the script.
-        addVariable("[" + Reserved.PARAMS + "]", Definition.getType("Map"), Reserved.PARAMS, true, true);
+        addVariable(null, Definition.getType("Map"), Reserved.PARAMS, true, true);
 
         // Scorer parameter passed to the script.  Internal use only.
-        addVariable("[" + Reserved.SCORER + "]", Definition.DEF_TYPE, Reserved.SCORER, true, true);
+        addVariable(null, Definition.DEF_TYPE, Reserved.SCORER, true, true);
 
         // Doc parameter passed to the script. TODO: Currently working as a Map, we can do better?
-        addVariable("[" + Reserved.DOC + "]", Definition.getType("Map"), Reserved.DOC, true, true);
+        addVariable(null, Definition.getType("Map"), Reserved.DOC, true, true);
 
         // Aggregation _value parameter passed to the script.
-        addVariable("[" + Reserved.VALUE + "]", Definition.DEF_TYPE, Reserved.VALUE, true, true);
+        addVariable(null, Definition.DEF_TYPE, Reserved.VALUE, true, true);
 
         // Shortcut variables.
 
         // Document's score as a read-only double.
         if (reserved.score) {
-            addVariable("[" + Reserved.SCORE + "]", Definition.DOUBLE_TYPE, Reserved.SCORE, true, true);
+            addVariable(null, Definition.DOUBLE_TYPE, Reserved.SCORE, true, true);
         }
 
         // The ctx map set by executable scripts as a read-only map.
         if (reserved.ctx) {
-            addVariable("[" + Reserved.CTX + "]", Definition.getType("Map"), Reserved.CTX, true, true);
+            addVariable(null, Definition.getType("Map"), Reserved.CTX, true, true);
         }
 
         // Loop counter to catch infinite loops.  Internal use only.
         if (reserved.loop) {
-            addVariable("[" + Reserved.LOOP + "]", Definition.INT_TYPE, Reserved.LOOP, true, true);
+            addVariable(null, Definition.INT_TYPE, Reserved.LOOP, true, true);
         }
     }
 
@@ -137,19 +138,20 @@ public final class Variables {
 
     public void decrementScope() {
         int remove = scopes.pop();
-
+        
         while (remove > 0) {
-             Variable variable = variables.pop();
-
+            Variable variable = variables.pop();
+            
+            // TODO: is this working? the code reads backwards...
             if (variable.read) {
-                throw new IllegalArgumentException("Error [" + variable.location + "]: Variable [" + variable.name + "] never used.");
+                throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] never used."));
             }
-
+            
             --remove;
         }
     }
 
-    public Variable getVariable(String location, String name) {
+    public Variable getVariable(Location location, String name) {
          Iterator<Variable> itr = variables.iterator();
 
         while (itr.hasNext()) {
@@ -160,20 +162,20 @@ public final class Variables {
             }
         }
 
-        if (location != null) {
-            throw new IllegalArgumentException("Error " + location + ": Variable [" + name + "] not defined.");
-        }
+        throw location.createError(new IllegalArgumentException("Variable [" + name + "] not defined."));
+    }
 
-        return null;
+    private boolean variableExists(String name) {
+        return variables.contains(name);
     }
 
-    public Variable addVariable(String location, Type type, String name, boolean readonly, boolean reserved) {
+    public Variable addVariable(Location location, Type type, String name, boolean readonly, boolean reserved) {
         if (!reserved && this.reserved.isReserved(name)) {
-            throw new IllegalArgumentException("Error " + location + ": Variable name [" + name + "] is reserved.");
+            throw location.createError(new IllegalArgumentException("Variable name [" + name + "] is reserved."));
         }
 
-        if (getVariable(null, name) != null) {
-            throw new IllegalArgumentException("Error " + location + ": Variable name [" + name + "] already defined.");
+        if (variableExists(name)) {
+            throw new IllegalArgumentException("Variable name [" + name + "] already defined.");
         }
 
         try {

+ 1 - 44
modules/lang-painless/src/main/java/org/elasticsearch/painless/Writer.java

@@ -72,9 +72,6 @@ final class Writer {
         writeEnd();
     }
 
-    // This maximum length is theoretically 65535 bytes, but as it's CESU-8 encoded we dont know how large it is in bytes, so be safe
-    private static final int MAX_NAME_LENGTH = 256;
-
     private void writeBegin() {
         final int version = Opcodes.V1_8;
         final int access = Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER | Opcodes.ACC_FINAL;
@@ -86,47 +83,7 @@ final class Writer {
             new String[] { WriterConstants.NEEDS_SCORE_TYPE.getInternalName() } : null;
 
         writer.visit(version, access, name, null, base, interfaces);
-        writer.visitSource(computeSourceName(), null);
-    }
-
-    /** Computes the file name (mostly important for stacktraces) */
-    private String computeSourceName() {
-        StringBuilder fileName = new StringBuilder();
-        if (scriptName.equals(PainlessScriptEngineService.INLINE_NAME)) {
-            // its an anonymous script, include at least a portion of the source to help identify which one it is
-            // but don't create stacktraces with filenames that contain newlines or huge names.
-
-            // truncate to the first newline
-            int limit = source.indexOf('\n');
-            if (limit >= 0) {
-                int limit2 = source.indexOf('\r');
-                if (limit2 >= 0) {
-                    limit = Math.min(limit, limit2);
-                }
-            } else {
-                limit = source.length();
-            }
-
-            // truncate to our limit
-            limit = Math.min(limit, MAX_NAME_LENGTH);
-            fileName.append(source, 0, limit);
-
-            // if we truncated, make it obvious
-            if (limit != source.length()) {
-                fileName.append(" ...");
-            }
-            fileName.append(" @ <inline script>");
-        } else {
-            // its a named script, just use the name
-            // but don't trust this has a reasonable length!
-            if (scriptName.length() > MAX_NAME_LENGTH) {
-                fileName.append(scriptName, 0, MAX_NAME_LENGTH);
-                fileName.append(" ...");
-            } else {
-                fileName.append(scriptName);
-            }
-        }
-        return fileName.toString();
+        writer.visitSource(Location.computeSourceName(scriptName,source), null);
     }
 
     private void writeConstructor() {

+ 6 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ErrorHandlingLexer.java

@@ -22,16 +22,17 @@ package org.elasticsearch.painless.antlr;
 import org.antlr.v4.runtime.CharStream;
 import org.antlr.v4.runtime.LexerNoViableAltException;
 import org.antlr.v4.runtime.misc.Interval;
-
-import java.text.ParseException;
+import org.elasticsearch.painless.Location;
 
 /**
  * A lexer that will override the default error behavior to fail on the first error.
  */
 final class ErrorHandlingLexer extends PainlessLexer {
+    final String sourceName;
 
-    ErrorHandlingLexer(final CharStream charStream) {
+    ErrorHandlingLexer(CharStream charStream, String sourceName) {
         super(charStream);
+        this.sourceName = sourceName;
     }
 
     @Override
@@ -40,11 +41,7 @@ final class ErrorHandlingLexer extends PainlessLexer {
         final int startIndex = lnvae.getStartIndex();
         final String text = charStream.getText(Interval.of(startIndex, charStream.index()));
 
-        final ParseException parseException = new ParseException("Error [" + _tokenStartLine + ":" +
-                _tokenStartCharPositionInLine + "]: unexpected character [" +
-                getErrorDisplay(text) + "].",  _tokenStartCharIndex);
-        parseException.initCause(lnvae);
-
-        throw new RuntimeException(parseException);
+        Location location = new Location(sourceName, _tokenStartCharIndex);
+        throw location.createError(new IllegalArgumentException("unexpected character [" + getErrorDisplay(text) + "].", lnvae));
     }
 }

+ 16 - 18
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/ParserErrorStrategy.java

@@ -25,13 +25,17 @@ import org.antlr.v4.runtime.NoViableAltException;
 import org.antlr.v4.runtime.Parser;
 import org.antlr.v4.runtime.RecognitionException;
 import org.antlr.v4.runtime.Token;
-
-import java.text.ParseException;
+import org.elasticsearch.painless.Location;
 
 /**
  * An error strategy that will override the default error behavior to fail on the first parser error.
  */
 final class ParserErrorStrategy extends DefaultErrorStrategy {
+    final String sourceName;
+    
+    ParserErrorStrategy(String sourceName) {
+        this.sourceName = sourceName;
+    }
 
     @Override
     public void recover(final Parser recognizer, final RecognitionException re) {
@@ -39,38 +43,32 @@ final class ParserErrorStrategy extends DefaultErrorStrategy {
         String message;
 
         if (token == null) {
-            message = "Error: no parse token found.";
+            message = "no parse token found.";
         } else if (re instanceof InputMismatchException) {
-            message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
-                    " unexpected token [" + getTokenErrorDisplay(token) + "]" +
+            message = "unexpected token [" + getTokenErrorDisplay(token) + "]" +
                     " was expecting one of [" + re.getExpectedTokens().toString(recognizer.getVocabulary()) + "].";
         } else if (re instanceof NoViableAltException) {
             if (token.getType() == PainlessParser.EOF) {
-                message = "Error: unexpected end of script.";
+                message = "unexpected end of script.";
             } else {
-                message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
-                        "invalid sequence of tokens near [" + getTokenErrorDisplay(token) + "].";
+                message = "invalid sequence of tokens near [" + getTokenErrorDisplay(token) + "].";
             }
         } else {
-            message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
-                    " unexpected token near [" + getTokenErrorDisplay(token) + "].";
+            message =  "unexpected token near [" + getTokenErrorDisplay(token) + "].";
         }
 
-        final ParseException parseException = new ParseException(message, token == null ? -1 : token.getStartIndex());
-        parseException.initCause(re);
-
-        throw new RuntimeException(parseException);
+        Location location = new Location(sourceName, token == null ? -1 : token.getStartIndex());
+        throw location.createError(new IllegalArgumentException(message, re));
     }
 
     @Override
     public Token recoverInline(final Parser recognizer) throws RecognitionException {
         final Token token = recognizer.getCurrentToken();
-        final String message = "Error[" + token.getLine() + ":" + token.getCharPositionInLine() + "]:" +
-            " unexpected token [" + getTokenErrorDisplay(token) + "]" +
+        final String message = "unexpected token [" + getTokenErrorDisplay(token) + "]" +
             " was expecting one of [" + recognizer.getExpectedTokens().toString(recognizer.getVocabulary()) + "].";
-        final ParseException parseException = new ParseException(message, token.getStartIndex());
 
-        throw new RuntimeException(parseException);
+        Location location = new Location(sourceName, token.getStartIndex());
+        throw location.createError(new IllegalArgumentException(message));
     }
 
     @Override

+ 79 - 84
modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

@@ -28,6 +28,7 @@ import org.antlr.v4.runtime.RecognitionException;
 import org.antlr.v4.runtime.Recognizer;
 import org.antlr.v4.runtime.atn.PredictionMode;
 import org.elasticsearch.painless.CompilerSettings;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.Variables.Reserved;
 import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext;
@@ -135,25 +136,27 @@ import java.util.List;
  */
 public final class Walker extends PainlessParserBaseVisitor<Object> {
 
-    public static SSource buildPainlessTree(String source, Reserved reserved, CompilerSettings settings) {
-        return new Walker(source, reserved, settings).source;
+    public static SSource buildPainlessTree(String name, String sourceText, Reserved reserved, CompilerSettings settings) {
+        return new Walker(name, sourceText, reserved, settings).source;
     }
 
     private final Reserved reserved;
     private final SSource source;
     private final CompilerSettings settings;
+    private final String sourceName;
 
-    private Walker(String source, Reserved reserved, CompilerSettings settings) {
+    private Walker(String name, String sourceText, Reserved reserved, CompilerSettings settings) {
         this.reserved = reserved;
         this.settings = settings;
-        this.source = (SSource)visit(buildAntlrTree(source));
+        this.sourceName = Location.computeSourceName(name, sourceText);
+        this.source = (SSource)visit(buildAntlrTree(sourceText));
     }
 
     private SourceContext buildAntlrTree(String source) {
         ANTLRInputStream stream = new ANTLRInputStream(source);
-        PainlessLexer lexer = new ErrorHandlingLexer(stream);
+        PainlessLexer lexer = new ErrorHandlingLexer(stream, sourceName);
         PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
-        ParserErrorStrategy strategy = new ParserErrorStrategy();
+        ParserErrorStrategy strategy = new ParserErrorStrategy(sourceName);
 
         lexer.removeErrorListeners();
         parser.removeErrorListeners();
@@ -185,16 +188,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
     }
 
-    private int line(ParserRuleContext ctx) {
-        return ctx.getStart().getLine();
-    }
-
-    private int offset(ParserRuleContext ctx) {
-        return ctx.getStart().getStartIndex();
-    }
-
-    private String location(ParserRuleContext ctx) {
-        return "[ " + ctx.getStart().getLine() + " : " + ctx.getStart().getCharPositionInLine() + " ]";
+    private Location location(ParserRuleContext ctx) {
+        return new Location(sourceName, ctx.getStart().getStartIndex());
     }
 
     @Override
@@ -205,7 +200,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             statements.add((AStatement)visit(statement));
         }
 
-        return new SSource(line(ctx), offset(ctx), location(ctx), statements);
+        return new SSource(location(ctx), statements);
     }
 
     @Override
@@ -216,9 +211,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         if (ctx.trailer().size() > 1) {
             SBlock elseblock = (SBlock)visit(ctx.trailer(1));
 
-            return new SIfElse(line(ctx), offset(ctx), location(ctx), expression, ifblock, elseblock);
+            return new SIfElse(location(ctx), expression, ifblock, elseblock);
         } else {
-            return new SIf(line(ctx), offset(ctx), location(ctx), expression, ifblock);
+            return new SIf(location(ctx), expression, ifblock);
         }
     }
 
@@ -233,11 +228,11 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         if (ctx.trailer() != null) {
             SBlock block = (SBlock)visit(ctx.trailer());
 
-            return new SWhile(line(ctx), offset(ctx), location(ctx), settings.getMaxLoopCounter(), expression, block);
+            return new SWhile(location(ctx), settings.getMaxLoopCounter(), expression, block);
         } else if (ctx.empty() != null) {
-            return new SWhile(line(ctx), offset(ctx), location(ctx), settings.getMaxLoopCounter(), expression, null);
+            return new SWhile(location(ctx), settings.getMaxLoopCounter(), expression, null);
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException(" Illegal tree structure."));
         }
     }
 
@@ -250,7 +245,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         AExpression expression = (AExpression)visitExpression(ctx.expression());
         SBlock block = (SBlock)visit(ctx.block());
 
-        return new SDo(line(ctx), offset(ctx), location(ctx), settings.getMaxLoopCounter(), block, expression);
+        return new SDo(location(ctx), settings.getMaxLoopCounter(), block, expression);
     }
 
     @Override
@@ -266,13 +261,13 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         if (ctx.trailer() != null) {
             SBlock block = (SBlock)visit(ctx.trailer());
 
-            return new SFor(line(ctx), offset(ctx), location(ctx),
+            return new SFor(location(ctx),
                 settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
         } else if (ctx.empty() != null) {
-            return new SFor(line(ctx), offset(ctx), location(ctx),
+            return new SFor(location(ctx),
                 settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -283,19 +278,19 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
 
     @Override
     public Object visitContinue(ContinueContext ctx) {
-        return new SContinue(line(ctx), offset(ctx), location(ctx));
+        return new SContinue(location(ctx));
     }
 
     @Override
     public Object visitBreak(BreakContext ctx) {
-        return new SBreak(line(ctx), offset(ctx), location(ctx));
+        return new SBreak(location(ctx));
     }
 
     @Override
     public Object visitReturn(ReturnContext ctx) {
         AExpression expression = (AExpression)visitExpression(ctx.expression());
 
-        return new SReturn(line(ctx), offset(ctx), location(ctx), expression);
+        return new SReturn(location(ctx), expression);
     }
 
     @Override
@@ -307,21 +302,21 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             catches.add((SCatch)visit(trap));
         }
 
-        return new STry(line(ctx), offset(ctx), location(ctx), block, catches);
+        return new STry(location(ctx), block, catches);
     }
 
     @Override
     public Object visitThrow(ThrowContext ctx) {
         AExpression expression = (AExpression)visitExpression(ctx.expression());
 
-        return new SThrow(line(ctx), offset(ctx), location(ctx), expression);
+        return new SThrow(location(ctx), expression);
     }
 
     @Override
     public Object visitExpr(ExprContext ctx) {
         AExpression expression = (AExpression)visitExpression(ctx.expression());
 
-        return new SExpression(line(ctx), offset(ctx), location(ctx), expression);
+        return new SExpression(location(ctx), expression);
     }
 
     @Override
@@ -332,9 +327,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             List<AStatement> statements = new ArrayList<>();
             statements.add((AStatement)visit(ctx.statement()));
 
-            return new SBlock(line(ctx), offset(ctx), location(ctx), statements);
+            return new SBlock(location(ctx), statements);
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -349,13 +344,13 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
                 statements.add((AStatement)visit(statement));
             }
 
-            return new SBlock(line(ctx), offset(ctx), location(ctx), statements);
+            return new SBlock(location(ctx), statements);
         }
     }
 
     @Override
     public Object visitEmpty(EmptyContext ctx) {
-        throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+        throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
@@ -365,7 +360,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.expression() != null) {
             return visitExpression(ctx.expression());
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -383,25 +378,25 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             String name = declvar.ID().getText();
             AExpression expression = declvar.expression() == null ? null : (AExpression)visitExpression(declvar.expression());
 
-            declarations.add(new SDeclaration(line(declvar), offset(declvar), location(declvar), type, name, expression));
+            declarations.add(new SDeclaration(location(declvar), type, name, expression));
         }
 
-        return new SDeclBlock(line(ctx), offset(ctx), location(ctx), declarations);
+        return new SDeclBlock(location(ctx), declarations);
     }
 
     @Override
     public Object visitDecltype(DecltypeContext ctx) {
-        throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+        throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
     public Object visitFuncref(FuncrefContext ctx) {
-        return new EFunctionRef(line(ctx), offset(ctx), location(ctx), ctx.TYPE().getText(), ctx.ID().getText());
+        return new EFunctionRef(location(ctx), ctx.TYPE().getText(), ctx.ID().getText());
     }
 
     @Override
     public Object visitDeclvar(DeclvarContext ctx) {
-        throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+        throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
@@ -410,12 +405,12 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         String name = ctx.ID().getText();
         SBlock block = (SBlock)visit(ctx.block());
 
-        return new SCatch(line(ctx), offset(ctx), location(ctx), type, name, block);
+        return new SCatch(location(ctx), type, name, block);
     }
 
     @Override
     public Object visitDelimiter(DelimiterContext ctx) {
-        throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+        throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
     }
 
     private Object visitExpression(ExpressionContext ctx) {
@@ -425,7 +420,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             @SuppressWarnings("unchecked")
             List<ALink> links = (List<ALink>)expression;
 
-            return new EChain(line(ctx), offset(ctx), location(ctx), links, false, false, null, null);
+            return new EChain(location(ctx), links, false, false, null, null);
         } else {
             return expression;
         }
@@ -465,10 +460,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.BWOR() != null) {
             operation = Operation.BWOR;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+            throw location(ctx).createError(new IllegalStateException("Unexpected state."));
         }
 
-        return new EBinary(line(ctx), offset(ctx), location(ctx), operation, left, right);
+        return new EBinary(location(ctx), operation, left, right);
     }
 
     @Override
@@ -494,10 +489,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.NER() != null) {
             operation = Operation.NER;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+            throw location(ctx).createError(new IllegalStateException("Unexpected state."));
         }
 
-        return new EComp(line(ctx), offset(ctx), location(ctx), operation, left, right);
+        return new EComp(location(ctx), operation, left, right);
     }
 
     @Override
@@ -511,10 +506,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.BOOLOR() != null) {
             operation = Operation.OR;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Unexpected state.");
+            throw location(ctx).createError(new IllegalStateException("Unexpected state."));
         }
 
-        return new EBool(line(ctx), offset(ctx), location(ctx), operation, left, right);
+        return new EBool(location(ctx), operation, left, right);
     }
 
     @Override
@@ -523,7 +518,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         AExpression left = (AExpression)visitExpression(ctx.expression(1));
         AExpression right = (AExpression)visitExpression(ctx.expression(2));
 
-        return new EConditional(line(ctx), offset(ctx), location(ctx), condition, left, right);
+        return new EConditional(location(ctx), condition, left, right);
     }
 
     @Override
@@ -557,12 +552,12 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.AOR() != null) {
             operation = Operation.BWOR;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
 
         AExpression expression = (AExpression)visitExpression(ctx.expression());
 
-        return new EChain(line(ctx), offset(ctx), location(ctx), links, false, false, operation, expression);
+        return new EChain(location(ctx), links, false, false, operation, expression);
     }
 
     private Object visitUnary(UnaryContext ctx) {
@@ -572,7 +567,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             @SuppressWarnings("unchecked")
             List<ALink> links = (List<ALink>)expression;
 
-            return new EChain(line(ctx), offset(ctx), location(ctx), links, false, false, null, null);
+            return new EChain(location(ctx), links, false, false, null, null);
         } else {
             return expression;
         }
@@ -589,10 +584,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.DECR() != null) {
             operation = Operation.DECR;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
 
-        return new EChain(line(ctx), offset(ctx), location(ctx), links, true, false, operation, null);
+        return new EChain(location(ctx), links, true, false, operation, null);
     }
 
     @Override
@@ -606,10 +601,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.DECR() != null) {
             operation = Operation.DECR;
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
 
-        return new EChain(line(ctx), offset(ctx), location(ctx), links, false, true, operation, null);
+        return new EChain(location(ctx), links, false, true, operation, null);
     }
 
     @Override
@@ -622,31 +617,31 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         final boolean negate = ctx.parent instanceof OperatorContext && ((OperatorContext)ctx.parent).SUB() != null;
 
         if (ctx.DECIMAL() != null) {
-            return new EDecimal(line(ctx), offset(ctx), location(ctx), (negate ? "-" : "") + ctx.DECIMAL().getText());
+            return new EDecimal(location(ctx), (negate ? "-" : "") + ctx.DECIMAL().getText());
         } else if (ctx.HEX() != null) {
-            return new ENumeric(line(ctx), offset(ctx), location(ctx), (negate ? "-" : "") + ctx.HEX().getText().substring(2), 16);
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.HEX().getText().substring(2), 16);
         } else if (ctx.INTEGER() != null) {
-            return new ENumeric(line(ctx), offset(ctx), location(ctx), (negate ? "-" : "") + ctx.INTEGER().getText(), 10);
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.INTEGER().getText(), 10);
         } else if (ctx.OCTAL() != null) {
-            return new ENumeric(line(ctx), offset(ctx), location(ctx), (negate ? "-" : "") + ctx.OCTAL().getText().substring(1), 8);
+            return new ENumeric(location(ctx), (negate ? "-" : "") + ctx.OCTAL().getText().substring(1), 8);
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + ": Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
     @Override
     public Object visitTrue(TrueContext ctx) {
-        return new EBoolean(line(ctx), offset(ctx), location(ctx), true);
+        return new EBoolean(location(ctx), true);
     }
 
     @Override
     public Object visitFalse(FalseContext ctx) {
-        return new EBoolean(line(ctx), offset(ctx), location(ctx), false);
+        return new EBoolean(location(ctx), false);
     }
 
     @Override
     public Object visitNull(NullContext ctx) {
-        return new ENull(line(ctx), offset(ctx), location(ctx));
+        return new ENull(location(ctx));
     }
 
     @Override
@@ -666,10 +661,10 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
             } else if (ctx.SUB() != null) {
                 operation = Operation.SUB;
             } else {
-                throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+                throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
             }
 
-            return new EUnary(line(ctx), offset(ctx), location(ctx), operation, expression);
+            return new EUnary(location(ctx), operation, expression);
         }
     }
 
@@ -681,11 +676,11 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         if (child instanceof List) {
             @SuppressWarnings("unchecked")
             List<ALink> links = (List<ALink>)child;
-            links.add(new LCast(line(ctx), offset(ctx), location(ctx), type));
+            links.add(new LCast(location(ctx), type));
 
             return links;
         } else {
-            return new EExplicit(line(ctx), offset(ctx), location(ctx), type, (AExpression)child);
+            return new EExplicit(location(ctx), type, (AExpression)child);
         }
     }
 
@@ -703,7 +698,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
 
             return links;
         } else if (!ctx.secondary().isEmpty()) {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         } else {
             return child;
         }
@@ -714,7 +709,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         String type = ctx.decltype().getText();
         List<ALink> links = new ArrayList<>();
 
-        links.add(new LStatic(line(ctx), offset(ctx), location(ctx), type));
+        links.add(new LStatic(location(ctx), type));
         links.add((ALink)visit(ctx.dot()));
 
         for (SecondaryContext secondary : ctx.secondary()) {
@@ -734,7 +729,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         }
 
         List<ALink> links = new ArrayList<>();
-        links.add(new LNewArray(line(ctx), offset(ctx), location(ctx), type, expressions));
+        links.add(new LNewArray(location(ctx), type, expressions));
 
         if (ctx.dot() != null) {
             links.add((ALink)visit(ctx.dot()));
@@ -743,7 +738,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
                 links.add((ALink)visit(secondary));
             }
         } else if (!ctx.secondary().isEmpty()) {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
 
         return links;
@@ -763,7 +758,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
     public Object visitString(StringContext ctx) {
         String string = ctx.STRING().getText().substring(1, ctx.STRING().getText().length() - 1);
         List<ALink> links = new ArrayList<>();
-        links.add(new LString(line(ctx), offset(ctx), location(ctx), string));
+        links.add(new LString(location(ctx), string));
 
         return links;
     }
@@ -772,7 +767,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
     public Object visitVariable(VariableContext ctx) {
         String name = ctx.ID().getText();
         List<ALink> links = new ArrayList<>();
-        links.add(new LVariable(line(ctx), offset(ctx), location(ctx), name));
+        links.add(new LVariable(location(ctx), name));
 
         reserved.markReserved(name);
 
@@ -786,7 +781,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         List<AExpression> arguments = (List<AExpression>)visit(ctx.arguments());
 
         List<ALink> links = new ArrayList<>();
-        links.add(new LNewObj(line(ctx), offset(ctx), location(ctx), type, arguments));
+        links.add(new LNewObj(location(ctx), type, arguments));
 
         return links;
     }
@@ -798,7 +793,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.brace() != null) {
             return visit(ctx.brace());
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -808,7 +803,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         @SuppressWarnings("unchecked")
         List<AExpression> arguments = (List<AExpression>)visit(ctx.arguments());
 
-        return new LCall(line(ctx), offset(ctx), location(ctx), name, arguments);
+        return new LCall(location(ctx), name, arguments);
     }
 
     @Override
@@ -820,17 +815,17 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.DOTINTEGER() != null) {
             value = ctx.DOTINTEGER().getText();
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
 
-        return new LField(line(ctx), offset(ctx), location(ctx), value);
+        return new LField(location(ctx), value);
     }
 
     @Override
     public Object visitBraceaccess(BraceaccessContext ctx) {
         AExpression expression = (AExpression)visitExpression(ctx.expression());
 
-        return new LBrace(line(ctx), offset(ctx), location(ctx), expression);
+        return new LBrace(location(ctx), expression);
     }
 
     @Override
@@ -851,7 +846,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
         } else if (ctx.funcref() != null) {
             return visit(ctx.funcref());
         } else {
-            throw new IllegalStateException("Error " + location(ctx) + " Illegal tree structure.");
+            throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 }

+ 12 - 11
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AExpression.java

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition.Cast;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
@@ -98,8 +99,8 @@ public abstract class AExpression extends ANode {
      */
     protected Label fals = null;
 
-    public AExpression(int line, int offset, String location) {
-        super(line, offset, location);
+    public AExpression(Location location) {
+        super(location);
     }
 
     /**
@@ -124,18 +125,18 @@ public abstract class AExpression extends ANode {
             if (constant == null || this instanceof EConstant) {
                 return this;
             } else {
-                final EConstant econstant = new EConstant(line, offset, location, constant);
+                final EConstant econstant = new EConstant(location, constant);
                 econstant.analyze(variables);
 
                 if (!expected.equals(econstant.actual)) {
-                    throw new IllegalStateException(error("Illegal tree structure."));
+                    throw createError(new IllegalStateException("Illegal tree structure."));
                 }
 
                 return econstant;
             }
         } else {
             if (constant == null) {
-                final ECast ecast = new ECast(line, offset, location, this, cast);
+                final ECast ecast = new ECast(location, this, cast);
                 ecast.statement = statement;
                 ecast.actual = expected;
                 ecast.isNull = isNull;
@@ -145,28 +146,28 @@ public abstract class AExpression extends ANode {
                 if (expected.sort.constant) {
                     constant = AnalyzerCaster.constCast(location, constant, cast);
 
-                    final EConstant econstant = new EConstant(line, offset, location, constant);
+                    final EConstant econstant = new EConstant(location, constant);
                     econstant.analyze(variables);
 
                     if (!expected.equals(econstant.actual)) {
-                        throw new IllegalStateException(error("Illegal tree structure."));
+                        throw createError(new IllegalStateException("Illegal tree structure."));
                     }
 
                     return econstant;
                 } else if (this instanceof EConstant) {
-                    final ECast ecast = new ECast(line, offset, location, this, cast);
+                    final ECast ecast = new ECast(location, this, cast);
                     ecast.actual = expected;
 
                     return ecast;
                 } else {
-                    final EConstant econstant = new EConstant(line, offset, location, constant);
+                    final EConstant econstant = new EConstant(location, constant);
                     econstant.analyze(variables);
 
                     if (!actual.equals(econstant.actual)) {
-                        throw new IllegalStateException(error("Illegal tree structure."));
+                        throw createError(new IllegalStateException("Illegal tree structure."));
                     }
 
-                    final ECast ecast = new ECast(line, offset, location, econstant, cast);
+                    final ECast ecast = new ECast(location, econstant, cast);
                     ecast.actual = expected;
 
                     return ecast;

+ 3 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ALink.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -73,8 +74,8 @@ public abstract class ALink extends ANode {
      */
     String string = null;
 
-    ALink(int line, int offset, String location, int size) {
-        super(line, offset, location);
+    ALink(Location location, int size) {
+        super(location);
 
         this.size = size;
     }

+ 8 - 19
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java

@@ -19,33 +19,22 @@
 
 package org.elasticsearch.painless.node;
 
+import org.elasticsearch.painless.Location;
+
 /**
  * The superclass for all other nodes.
  */
 public abstract class ANode {
-
-    /**
-     * The line number in the original source used for debugging and errors.
-     */
-    final int line;
-
     /**
-     * The character offset in the original source used for debugging and errors.
+     * The identifier of the script and character offset used for debugging and errors.
      */
-    final int offset;
+    final Location location;
 
-    /**
-     * The location in the original source to be printed in error messages.
-     */
-    final String location;
-
-    ANode(int line, int offset, String location) {
-        this.line = line;
-        this.offset = offset;
+    ANode(Location location) {
         this.location = location;
     }
-
-    public String error(final String message) {
-        return "Error " + location  + ": " + message;
+    
+    public RuntimeException createError(RuntimeException exception) {
+        return location.createError(exception);
     }
 }

+ 3 - 2
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStatement.java

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 /**
@@ -107,8 +108,8 @@ public abstract class AStatement extends ANode {
      */
     Label brake = null;
 
-    AStatement(int line, int offset, String location) {
-        super(line, offset, location);
+    AStatement(Location location) {
+        super(location);
     }
 
     /**

+ 53 - 44
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBinary.java

@@ -23,6 +23,7 @@ import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Definition;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.Variables;
@@ -38,8 +39,8 @@ public final class EBinary extends AExpression {
 
     boolean cat = false;
 
-    public EBinary(int line, int offset, String location, Operation operation, AExpression left, AExpression right) {
-        super(line, offset, location);
+    public EBinary(Location location, Operation operation, AExpression left, AExpression right) {
+        super(location);
 
         this.operation = operation;
         this.left = left;
@@ -71,7 +72,7 @@ public final class EBinary extends AExpression {
         } else if (operation == Operation.BWOR) {
             analyzeBWOr(variables);
         } else {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -82,7 +83,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply multiply [*] to types " +
+            throw createError(new ClassCastException("Cannot apply multiply [*] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -104,7 +105,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant * (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -118,7 +119,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply divide [/] to types " +
+            throw createError(new ClassCastException("Cannot apply divide [/] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -131,16 +132,20 @@ public final class EBinary extends AExpression {
         if (left.constant != null && right.constant != null) {
             Sort sort = promote.sort;
 
-            if (sort == Sort.INT) {
-                constant = (int)left.constant / (int)right.constant;
-            } else if (sort == Sort.LONG) {
-                constant = (long)left.constant / (long)right.constant;
-            } else if (sort == Sort.FLOAT) {
-                constant = (float)left.constant / (float)right.constant;
-            } else if (sort == Sort.DOUBLE) {
-                constant = (double)left.constant / (double)right.constant;
-            } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+            try {
+                if (sort == Sort.INT) {
+                    constant = (int)left.constant / (int)right.constant;
+                } else if (sort == Sort.LONG) {
+                    constant = (long)left.constant / (long)right.constant;
+                } else if (sort == Sort.FLOAT) {
+                    constant = (float)left.constant / (float)right.constant;
+                } else if (sort == Sort.DOUBLE) {
+                    constant = (double)left.constant / (double)right.constant;
+                } else {
+                    throw createError(new IllegalStateException("Illegal tree structure."));
+                }
+            } catch (ArithmeticException e) {
+                throw createError(e);
             }
         }
 
@@ -154,7 +159,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply remainder [%] to types " +
+            throw createError(new ClassCastException("Cannot apply remainder [%] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -167,16 +172,20 @@ public final class EBinary extends AExpression {
         if (left.constant != null && right.constant != null) {
             Sort sort = promote.sort;
 
-            if (sort == Sort.INT) {
-                constant = (int)left.constant % (int)right.constant;
-            } else if (sort == Sort.LONG) {
-                constant = (long)left.constant % (long)right.constant;
-            } else if (sort == Sort.FLOAT) {
-                constant = (float)left.constant % (float)right.constant;
-            } else if (sort == Sort.DOUBLE) {
-                constant = (double)left.constant % (double)right.constant;
-            } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+            try {
+                if (sort == Sort.INT) {
+                    constant = (int)left.constant % (int)right.constant;
+                } else if (sort == Sort.LONG) {
+                    constant = (long)left.constant % (long)right.constant;
+                } else if (sort == Sort.FLOAT) {
+                    constant = (float)left.constant % (float)right.constant;
+                } else if (sort == Sort.DOUBLE) {
+                    constant = (double)left.constant % (double)right.constant;
+                } else {
+                    throw createError(new IllegalStateException("Illegal tree structure."));
+                }
+            } catch (ArithmeticException e) {
+                throw createError(e);
             }
         }
 
@@ -190,7 +199,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteAdd(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply add [+] to types " +
+            throw createError(new ClassCastException("Cannot apply add [+] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -228,7 +237,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.STRING) {
                 constant = "" + left.constant + right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -242,7 +251,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply subtract [-] to types " +
+            throw createError(new ClassCastException("Cannot apply subtract [-] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -264,7 +273,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant - (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -278,7 +287,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply left shift [<<] to types " +
+            throw createError(new ClassCastException("Cannot apply left shift [<<] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -297,7 +306,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant << (int)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -311,7 +320,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply right shift [>>] to types " +
+            throw createError(new ClassCastException("Cannot apply right shift [>>] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -330,7 +339,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant >> (int)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -344,7 +353,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply unsigned shift [>>>] to types " +
+            throw createError(new ClassCastException("Cannot apply unsigned shift [>>>] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -363,7 +372,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant >>> (int)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -377,7 +386,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply and [&] to types " +
+            throw createError(new ClassCastException("Cannot apply and [&] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -395,7 +404,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant & (long)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -409,7 +418,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteXor(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply xor [^] to types " +
+            throw createError(new ClassCastException("Cannot apply xor [^] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -429,7 +438,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant ^ (long)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -443,7 +452,7 @@ public final class EBinary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply or [|] to types " +
+            throw createError(new ClassCastException("Cannot apply or [|] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -461,7 +470,7 @@ public final class EBinary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = (long)left.constant | (long)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -470,7 +479,7 @@ public final class EBinary extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (actual.sort == Sort.STRING && operation == Operation.ADD) {
             if (!cat) {
                 writer.writeNewStrings();

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBool.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
@@ -34,8 +35,8 @@ public final class EBool extends AExpression {
     AExpression left;
     AExpression right;
 
-    public EBool(int line, int offset, String location, Operation operation, AExpression left, AExpression right) {
-        super(line, offset, location);
+    public EBool(Location location, Operation operation, AExpression left, AExpression right) {
+        super(location);
 
         this.operation = operation;
         this.left = left;
@@ -58,7 +59,7 @@ public final class EBool extends AExpression {
             } else if (operation == Operation.OR) {
                 constant = (boolean)left.constant || (boolean)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -95,7 +96,7 @@ public final class EBool extends AExpression {
                     writer.mark(localtru);
                 }
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         } else {
             if (operation == Operation.AND) {
@@ -131,7 +132,7 @@ public final class EBool extends AExpression {
                 writer.push(false);
                 writer.mark(end);
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
     }

+ 4 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EBoolean.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -28,8 +29,8 @@ import org.elasticsearch.painless.MethodWriter;
  */
 public final class EBoolean extends AExpression {
 
-    public EBoolean(int line, int offset, String location, boolean constant) {
-        super(line, offset, location);
+    public EBoolean(Location location, boolean constant) {
+        super(location);
 
         this.constant = constant;
     }
@@ -41,6 +42,6 @@ public final class EBoolean extends AExpression {
 
     @Override
     void write(MethodWriter adapter) {
-        throw new IllegalArgumentException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition.Cast;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -34,8 +35,8 @@ final class ECast extends AExpression {
 
     Cast cast = null;
 
-    ECast(int line, int offset, String location, AExpression child, Cast cast) {
-        super(line, offset, location);
+    ECast(Location location, AExpression child, Cast cast) {
+        super(location);
 
         this.type = null;
         this.child = child;
@@ -45,13 +46,13 @@ final class ECast extends AExpression {
 
     @Override
     void analyze(Variables variables) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
     void write(MethodWriter writer) {
         child.write(writer);
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.writeCast(cast);
         writer.writeBranch(tru, fals);
     }

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

@@ -23,6 +23,7 @@ import org.elasticsearch.painless.Definition;
 import org.elasticsearch.painless.Definition.Cast;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.Variables;
@@ -46,9 +47,9 @@ public final class EChain extends AExpression {
     Cast there = null;
     Cast back = null;
 
-    public EChain(int line, int offset, String location, List<ALink> links,
+    public EChain(Location location, List<ALink> links,
                   boolean pre, boolean post, Operation operation, AExpression expression) {
-        super(line, offset, location);
+        super(location);
 
         this.links = links;
         this.pre = pre;
@@ -114,40 +115,40 @@ public final class EChain extends AExpression {
         ALink last = links.get(links.size() - 1);
 
         if (pre && post) {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         } else if (pre || post) {
             if (expression != null) {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
 
             Sort sort = last.after.sort;
 
             if (operation == Operation.INCR) {
                 if (sort == Sort.DOUBLE) {
-                    expression = new EConstant(line, offset, location, 1D);
+                    expression = new EConstant(location, 1D);
                 } else if (sort == Sort.FLOAT) {
-                    expression = new EConstant(line, offset, location, 1F);
+                    expression = new EConstant(location, 1F);
                 } else if (sort == Sort.LONG) {
-                    expression = new EConstant(line, offset, location, 1L);
+                    expression = new EConstant(location, 1L);
                 } else {
-                    expression = new EConstant(line, offset, location, 1);
+                    expression = new EConstant(location, 1);
                 }
 
                 operation = Operation.ADD;
             } else if (operation == Operation.DECR) {
                 if (sort == Sort.DOUBLE) {
-                    expression = new EConstant(line, offset, location, 1D);
+                    expression = new EConstant(location, 1D);
                 } else if (sort == Sort.FLOAT) {
-                    expression = new EConstant(line, offset, location, 1F);
+                    expression = new EConstant(location, 1F);
                 } else if (sort == Sort.LONG) {
-                    expression = new EConstant(line, offset, location, 1L);
+                    expression = new EConstant(location, 1L);
                 } else {
-                    expression = new EConstant(line, offset, location, 1);
+                    expression = new EConstant(location, 1);
                 }
 
                 operation = Operation.SUB;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
     }
@@ -180,12 +181,12 @@ public final class EChain extends AExpression {
         } else if (operation == Operation.BWOR) {
             promote = AnalyzerCaster.promoteXor(last.after, expression.actual);
         } else {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         }
 
         if (promote == null) {
-            throw new ClassCastException("Cannot apply compound assignment " +
-                "[" + operation.symbol + "=] to types [" + last.after + "] and [" + expression.actual + "].");
+            throw createError(new ClassCastException("Cannot apply compound assignment " +
+                "[" + operation.symbol + "=] to types [" + last.after + "] and [" + expression.actual + "]."));
         }
 
         cat = operation == Operation.ADD && promote.sort == Sort.STRING;
@@ -248,9 +249,8 @@ public final class EChain extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        if (cat) {
-            writer.writeDebugInfo(offset);
-        }
+        // can cause class cast exception among other things at runtime
+        writer.writeDebugInfo(location);
 
         if (cat) {
             writer.writeNewStrings();

+ 28 - 27
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EComp.java

@@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
 import org.elasticsearch.painless.Definition;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.Variables;
@@ -46,8 +47,8 @@ public final class EComp extends AExpression {
     AExpression left;
     AExpression right;
 
-    public EComp(int line, int offset, String location, Operation operation, AExpression left, AExpression right) {
-        super(line, offset, location);
+    public EComp(Location location, Operation operation, AExpression left, AExpression right) {
+        super(location);
 
         this.operation = operation;
         this.left = left;
@@ -73,7 +74,7 @@ public final class EComp extends AExpression {
         } else if (operation == Operation.LT) {
             analyzeLT(variables);
         } else {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -84,7 +85,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply equals [==] to types " +
+            throw createError(new ClassCastException("Cannot apply equals [==] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -95,7 +96,7 @@ public final class EComp extends AExpression {
         right = right.cast(variables);
 
         if (left.isNull && right.isNull) {
-            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+            throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
         }
 
         if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
@@ -116,7 +117,7 @@ public final class EComp extends AExpression {
             } else if (!right.isNull) {
                 constant = right.constant.equals(null);
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -130,7 +131,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply reference equals [===] to types " +
+            throw createError(new ClassCastException("Cannot apply reference equals [===] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -141,7 +142,7 @@ public final class EComp extends AExpression {
         right = right.cast(variables);
 
         if (left.isNull && right.isNull) {
-            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+            throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
         }
 
         if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
@@ -172,7 +173,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply not equals [!=] to types " +
+            throw createError(new ClassCastException("Cannot apply not equals [!=] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -183,7 +184,7 @@ public final class EComp extends AExpression {
         right = right.cast(variables);
 
         if (left.isNull && right.isNull) {
-            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+            throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
         }
 
         if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
@@ -204,7 +205,7 @@ public final class EComp extends AExpression {
             } else if (!right.isNull) {
                 constant = !right.constant.equals(null);
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -218,7 +219,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteEquality(left.actual, right.actual);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply reference not equals [!==] to types " +
+            throw createError(new ClassCastException("Cannot apply reference not equals [!==] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -229,7 +230,7 @@ public final class EComp extends AExpression {
         right = right.cast(variables);
 
         if (left.isNull && right.isNull) {
-            throw new IllegalArgumentException(error("Extraneous comparison of null constants."));
+            throw createError(new IllegalArgumentException("Extraneous comparison of null constants."));
         }
 
         if ((left.constant != null || left.isNull) && (right.constant != null || right.isNull)) {
@@ -260,7 +261,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply greater than or equals [>=] to types " +
+            throw createError(new ClassCastException("Cannot apply greater than or equals [>=] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -282,7 +283,7 @@ public final class EComp extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant >= (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -296,7 +297,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply greater than [>] to types " +
+            throw createError(new ClassCastException("Cannot apply greater than [>] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -318,7 +319,7 @@ public final class EComp extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant > (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -332,7 +333,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply less than or equals [<=] to types " +
+            throw createError(new ClassCastException("Cannot apply less than or equals [<=] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -354,7 +355,7 @@ public final class EComp extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant <= (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -368,7 +369,7 @@ public final class EComp extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(left.actual, right.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply less than [>=] to types " +
+            throw createError(new ClassCastException("Cannot apply less than [>=] to types " +
                 "[" + left.actual.name + "] and [" + right.actual.name + "]."));
         }
 
@@ -390,7 +391,7 @@ public final class EComp extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = (double)left.constant < (double)right.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -399,7 +400,7 @@ public final class EComp extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         boolean branch = tru != null || fals != null;
         org.objectweb.asm.Type rtype = right.actual.type;
         Sort rsort = right.actual.sort;
@@ -429,12 +430,12 @@ public final class EComp extends AExpression {
             case BYTE:
             case SHORT:
             case CHAR:
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             case BOOL:
                 if      (eq) writer.ifZCmp(MethodWriter.EQ, jump);
                 else if (ne) writer.ifZCmp(MethodWriter.NE, jump);
                 else {
-                    throw new IllegalStateException(error("Illegal tree structure."));
+                    throw createError(new IllegalStateException("Illegal tree structure."));
                 }
 
                 break;
@@ -449,7 +450,7 @@ public final class EComp extends AExpression {
                 else if (gt)  writer.ifCmp(rtype, MethodWriter.GT, jump);
                 else if (gte) writer.ifCmp(rtype, MethodWriter.GE, jump);
                 else {
-                    throw new IllegalStateException(error("Illegal tree structure."));
+                    throw createError(new IllegalStateException("Illegal tree structure."));
                 }
 
                 break;
@@ -485,7 +486,7 @@ public final class EComp extends AExpression {
                     writer.invokeStatic(DEF_UTIL_TYPE, DEF_GTE_CALL);
                     writejump = false;
                 } else {
-                    throw new IllegalStateException(error("Illegal tree structure."));
+                    throw createError(new IllegalStateException("Illegal tree structure."));
                 }
 
                 if (branch && !writejump) {
@@ -518,7 +519,7 @@ public final class EComp extends AExpression {
                         writer.ifCmp(rtype, MethodWriter.NE, jump);
                     }
                 } else {
-                    throw new IllegalStateException(error("Illegal tree structure."));
+                    throw createError(new IllegalStateException("Illegal tree structure."));
                 }
         }
 

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

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
 import org.elasticsearch.painless.Definition.Type;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
@@ -35,8 +36,8 @@ public final class EConditional extends AExpression {
     AExpression left;
     AExpression right;
 
-    public EConditional(int line, int offset, String location, AExpression condition, AExpression left, AExpression right) {
-        super(line, offset, location);
+    public EConditional(Location location, AExpression condition, AExpression left, AExpression right) {
+        super(location);
 
         this.condition = condition;
         this.left = left;
@@ -50,7 +51,7 @@ public final class EConditional extends AExpression {
         condition = condition.cast(variables);
 
         if (condition.constant != null) {
-            throw new IllegalArgumentException(error("Extraneous conditional statement."));
+            throw createError(new IllegalArgumentException("Extraneous conditional statement."));
         }
 
         left.expected = expected;
@@ -78,7 +79,7 @@ public final class EConditional extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         Label localfals = new Label();
         Label end = new Label();
 

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EConstant.java

@@ -21,17 +21,18 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
 import org.elasticsearch.painless.Definition.Sort;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
 /**
- * Respresents a constant.  Note this replaces any other expression
+ * Represents a constant.  Note this replaces any other expression
  * node with a constant value set during a cast.  (Internal only.)
  */
 final class EConstant extends AExpression {
 
-    EConstant(int line, int offset, String location, Object constant) {
-        super(line, offset, location);
+    EConstant(Location location, Object constant) {
+        super(location);
 
         this.constant = constant;
     }
@@ -57,7 +58,7 @@ final class EConstant extends AExpression {
         } else if (constant instanceof Boolean) {
             actual = Definition.BOOLEAN_TYPE;
         } else {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -85,7 +86,7 @@ final class EConstant extends AExpression {
 
                 break;
             default:
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
         }
 
         if (sort != Sort.BOOL) {

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EDecimal.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -30,8 +31,8 @@ public final class EDecimal extends AExpression {
 
     final String value;
 
-    public EDecimal(int line, int offset, String location, String value) {
-        super(line, offset, location);
+    public EDecimal(Location location, String value) {
+        super(location);
 
         this.value = value;
     }
@@ -43,20 +44,20 @@ public final class EDecimal extends AExpression {
                 constant = Float.parseFloat(value.substring(0, value.length() - 1));
                 actual = Definition.FLOAT_TYPE;
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid float constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid float constant [" + value + "]."));
             }
         } else {
             try {
                 constant = Double.parseDouble(value);
                 actual = Definition.DOUBLE_TYPE;
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid double constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid double constant [" + value + "]."));
             }
         }
     }
 
     @Override
     void write(MethodWriter writer) {
-        throw new IllegalArgumentException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -31,8 +32,8 @@ public final class EExplicit extends AExpression {
     final String type;
     AExpression child;
 
-    public EExplicit(int line, int offset, String location, String type, AExpression child) {
-        super(line, offset, location);
+    public EExplicit(Location location, String type, AExpression child) {
+        super(location);
 
         this.type = type;
         this.child = child;
@@ -43,7 +44,7 @@ public final class EExplicit extends AExpression {
         try {
             actual = Definition.getType(this.type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
         child.expected = actual;
@@ -54,7 +55,7 @@ public final class EExplicit extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        throw new IllegalArgumentException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 
     AExpression cast(Variables variables) {

+ 6 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EFunctionRef.java

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.painless.node;
 
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Variables;
 
@@ -29,8 +30,8 @@ public class EFunctionRef extends AExpression {
     public String type;
     public String call;
 
-    public EFunctionRef(int line, int offset, String location, String type, String call) {
-        super(line, offset, location);
+    public EFunctionRef(Location location, String type, String call) {
+        super(location);
 
         this.type = type;
         this.call = call;
@@ -38,11 +39,12 @@ public class EFunctionRef extends AExpression {
 
     @Override
     void analyze(Variables variables) {
-        throw new UnsupportedOperationException(error("Function references [" + type + "::" + call + "] are not currently supported."));
+        throw createError(new UnsupportedOperationException("Function references [" + type + "::" + call + 
+                                                            "] are not currently supported."));
     }
 
     @Override
     void write(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 4 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENull.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Opcodes;
 import org.elasticsearch.painless.MethodWriter;
@@ -29,8 +30,8 @@ import org.elasticsearch.painless.MethodWriter;
  */
 public final class ENull extends AExpression {
 
-    public ENull(int line, int offset, String location) {
-        super(line, offset, location);
+    public ENull(Location location) {
+        super(location);
     }
 
     @Override
@@ -39,7 +40,7 @@ public final class ENull extends AExpression {
 
         if (expected != null) {
             if (expected.sort.primitive) {
-                throw new IllegalArgumentException(error("Cannot cast null to a primitive type [" + expected.name + "]."));
+                throw createError(new IllegalArgumentException("Cannot cast null to a primitive type [" + expected.name + "]."));
             }
 
             actual = expected;

+ 10 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ENumeric.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
@@ -32,8 +33,8 @@ public final class ENumeric extends AExpression {
     final String value;
     int radix;
 
-    public ENumeric(int line, int offset, String location, String value, int radix) {
-        super(line, offset, location);
+    public ENumeric(Location location, String value, int radix) {
+        super(location);
 
         this.value = value;
         this.radix = radix;
@@ -43,32 +44,32 @@ public final class ENumeric extends AExpression {
     void analyze(Variables variables) {
         if (value.endsWith("d") || value.endsWith("D")) {
             if (radix != 10) {
-                throw new IllegalStateException(error("Invalid tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
 
             try {
                 constant = Double.parseDouble(value.substring(0, value.length() - 1));
                 actual = Definition.DOUBLE_TYPE;
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid double constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid double constant [" + value + "]."));
             }
         } else if (value.endsWith("f") || value.endsWith("F")) {
             if (radix != 10) {
-                throw new IllegalStateException(error("Invalid tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
 
             try {
                 constant = Float.parseFloat(value.substring(0, value.length() - 1));
                 actual = Definition.FLOAT_TYPE;
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid float constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid float constant [" + value + "]."));
             }
         } else if (value.endsWith("l") || value.endsWith("L")) {
             try {
                 constant = Long.parseLong(value.substring(0, value.length() - 1), radix);
                 actual = Definition.LONG_TYPE;
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid long constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid long constant [" + value + "]."));
             }
         } else {
             try {
@@ -89,13 +90,13 @@ public final class ENumeric extends AExpression {
                     actual = Definition.INT_TYPE;
                 }
             } catch (NumberFormatException exception) {
-                throw new IllegalArgumentException(error("Invalid int constant [" + value + "]."));
+                throw createError(new IllegalArgumentException("Invalid int constant [" + value + "]."));
             }
         }
     }
 
     @Override
     void write(MethodWriter writer) {
-        throw new IllegalArgumentException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 13 - 12
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EUnary.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Type;
 import org.elasticsearch.painless.AnalyzerCaster;
@@ -40,8 +41,8 @@ public final class EUnary extends AExpression {
     final Operation operation;
     AExpression child;
 
-    public EUnary(int line, int offset, String location, Operation operation, AExpression child) {
-        super(line, offset, location);
+    public EUnary(Location location, Operation operation, AExpression child) {
+        super(location);
 
         this.operation = operation;
         this.child = child;
@@ -58,7 +59,7 @@ public final class EUnary extends AExpression {
         } else if (operation == Operation.SUB) {
             analyzerSub(variables);
         } else {
-            throw new IllegalStateException(error("Illegal tree structure."));
+            throw createError(new IllegalStateException("Illegal tree structure."));
         }
     }
 
@@ -80,7 +81,7 @@ public final class EUnary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(child.actual, false);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply not [~] to type [" + child.actual.name + "]."));
+            throw createError(new ClassCastException("Cannot apply not [~] to type [" + child.actual.name + "]."));
         }
 
         child.expected = promote;
@@ -94,7 +95,7 @@ public final class EUnary extends AExpression {
             } else if (sort == Sort.LONG) {
                 constant = ~(long)child.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -107,7 +108,7 @@ public final class EUnary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(child.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply positive [+] to type [" + child.actual.name + "]."));
+            throw createError(new ClassCastException("Cannot apply positive [+] to type [" + child.actual.name + "]."));
         }
 
         child.expected = promote;
@@ -125,7 +126,7 @@ public final class EUnary extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = +(double)child.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -138,7 +139,7 @@ public final class EUnary extends AExpression {
         Type promote = AnalyzerCaster.promoteNumeric(child.actual, true);
 
         if (promote == null) {
-            throw new ClassCastException(error("Cannot apply negative [-] to type [" + child.actual.name + "]."));
+            throw createError(new ClassCastException("Cannot apply negative [-] to type [" + child.actual.name + "]."));
         }
 
         child.expected = promote;
@@ -156,7 +157,7 @@ public final class EUnary extends AExpression {
             } else if (sort == Sort.DOUBLE) {
                 constant = -(double)child.constant;
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -165,7 +166,7 @@ public final class EUnary extends AExpression {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (operation == Operation.NOT) {
             if (tru == null && fals == null) {
                 Label localfals = new Label();
@@ -199,7 +200,7 @@ public final class EUnary extends AExpression {
                     } else if (sort == Sort.LONG) {
                         writer.push(-1L);
                     } else {
-                        throw new IllegalStateException(error("Illegal tree structure."));
+                        throw createError(new IllegalStateException("Illegal tree structure."));
                     }
 
                     writer.math(MethodWriter.XOR, type);
@@ -211,7 +212,7 @@ public final class EUnary extends AExpression {
                     writer.math(MethodWriter.NEG, type);
                 }
             } else if (operation != Operation.ADD) {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
 
             writer.writeBranch(tru, fals);

+ 8 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LArrayLength.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -30,8 +31,8 @@ public final class LArrayLength extends ALink {
 
     final String value;
 
-    LArrayLength(int line, int offset, String location, String value) {
-        super(line, offset, location, -1);
+    LArrayLength(Location location, String value) {
+        super(location, -1);
 
         this.value = value;
     }
@@ -40,14 +41,14 @@ public final class LArrayLength extends ALink {
     ALink analyze(Variables variables) {
         if ("length".equals(value)) {
             if (!load) {
-                throw new IllegalArgumentException(error("Must read array field [length]."));
+                throw createError(new IllegalArgumentException("Must read array field [length]."));
             } else if (store) {
-                throw new IllegalArgumentException(error("Cannot write to read-only array field [length]."));
+                throw createError(new IllegalArgumentException("Cannot write to read-only array field [length]."));
             }
 
             after = Definition.INT_TYPE;
         } else {
-            throw new IllegalArgumentException(error("Illegal field access [" + value + "]."));
+            throw createError(new IllegalArgumentException("Illegal field access [" + value + "]."));
         }
 
         return this;
@@ -60,12 +61,12 @@ public final class LArrayLength extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.arrayLength();
     }
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 10 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LBrace.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
@@ -34,8 +35,8 @@ public final class LBrace extends ALink {
 
     AExpression index;
 
-    public LBrace(int line, int offset, String location, AExpression index) {
-        super(line, offset, location, 2);
+    public LBrace(Location location, AExpression index) {
+        super(location, 2);
 
         this.index = index;
     }
@@ -43,7 +44,7 @@ public final class LBrace extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before == null) {
-            throw new IllegalArgumentException(error("Illegal array access made without target."));
+            throw createError(new IllegalArgumentException("Illegal array access made without target."));
         }
 
         final Sort sort = before.sort;
@@ -57,14 +58,14 @@ public final class LBrace extends ALink {
 
             return this;
         } else if (sort == Sort.DEF) {
-            return new LDefArray(line, offset, location, index).copy(this).analyze(variables);
+            return new LDefArray(location, index).copy(this).analyze(variables);
         } else if (Map.class.isAssignableFrom(before.clazz)) {
-            return new LMapShortcut(line, offset, location, index).copy(this).analyze(variables);
+            return new LMapShortcut(location, index).copy(this).analyze(variables);
         } else if (List.class.isAssignableFrom(before.clazz)) {
-            return new LListShortcut(line, offset, location, index).copy(this).analyze(variables);
+            return new LListShortcut(location, index).copy(this).analyze(variables);
         }
 
-        throw new IllegalArgumentException(error("Illegal array access on type [" + before.name + "]."));
+        throw createError(new IllegalArgumentException("Illegal array access on type [" + before.name + "]."));
     }
 
     @Override
@@ -74,13 +75,13 @@ public final class LBrace extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.arrayLoad(after.type);
     }
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.arrayStore(after.type);
     }
 

+ 10 - 9
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCall.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Method;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Struct;
@@ -38,8 +39,8 @@ public final class LCall extends ALink {
 
     Method method = null;
 
-    public LCall(int line, int offset, String location, String name, List<AExpression> arguments) {
-        super(line, offset, location, -1);
+    public LCall(Location location, String name, List<AExpression> arguments) {
+        super(location, -1);
 
         this.name = name;
         this.arguments = arguments;
@@ -48,11 +49,11 @@ public final class LCall extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before == null) {
-            throw new IllegalArgumentException(error("Illegal call [" + name + "] made without target."));
+            throw createError(new IllegalArgumentException("Illegal call [" + name + "] made without target."));
         } else if (before.sort == Sort.ARRAY) {
-            throw new IllegalArgumentException(error("Illegal call [" + name + "] on array type."));
+            throw createError(new IllegalArgumentException("Illegal call [" + name + "] on array type."));
         } else if (store) {
-            throw new IllegalArgumentException(error("Cannot assign a value to a call [" + name + "]."));
+            throw createError(new IllegalArgumentException("Cannot assign a value to a call [" + name + "]."));
         }
 
         Definition.MethodKey methodKey = new Definition.MethodKey(name, arguments.size());
@@ -74,13 +75,13 @@ public final class LCall extends ALink {
 
             return this;
         } else if (before.sort == Sort.DEF) {
-            ALink link = new LDefCall(line, offset, location, name, arguments);
+            ALink link = new LDefCall(location, name, arguments);
             link.copy(this);
 
             return link.analyze(variables);
         }
 
-        throw new IllegalArgumentException(error("Unknown call [" + name + "] with [" + arguments.size() +
+        throw createError(new IllegalArgumentException("Unknown call [" + name + "] with [" + arguments.size() +
                                                  "] arguments on type [" + struct.name + "]."));
     }
 
@@ -91,7 +92,7 @@ public final class LCall extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         for (AExpression argument : arguments) {
             argument.write(writer);
         }
@@ -111,6 +112,6 @@ public final class LCall extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 8 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LCast.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Cast;
 import org.elasticsearch.painless.AnalyzerCaster;
 import org.elasticsearch.painless.Variables;
@@ -34,8 +35,8 @@ public final class LCast extends ALink {
 
     Cast cast = null;
 
-    public LCast(int line, int offset, String location, String type) {
-        super(line, offset, location, -1);
+    public LCast(Location location, String type) {
+        super(location, -1);
 
         this.type = type;
     }
@@ -43,15 +44,15 @@ public final class LCast extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before == null) {
-            throw new IllegalStateException(error("Illegal cast without a target."));
+            throw createError(new IllegalStateException("Illegal cast without a target."));
         } else if (store) {
-            throw new IllegalArgumentException(error("Cannot assign a value to a cast."));
+            throw createError(new IllegalArgumentException("Cannot assign a value to a cast."));
         }
 
         try {
             after = Definition.getType(type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
         }
 
         cast = AnalyzerCaster.getLegalCast(location, before, after, true, false);
@@ -61,7 +62,7 @@ public final class LCast extends ALink {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.writeCast(cast);
     }
 
@@ -72,6 +73,6 @@ public final class LCast extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Type;
@@ -34,8 +35,8 @@ final class LDefArray extends ALink implements IDefLink {
 
     AExpression index;
 
-    LDefArray(int line, int offset, String location, AExpression index) {
-        super(line, offset, location, 2);
+    LDefArray(Location location, AExpression index) {
+        super(location, 2);
 
         this.index = index;
     }
@@ -58,14 +59,14 @@ final class LDefArray extends ALink implements IDefLink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
         writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_LOAD);
     }
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
         writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_STORE);
     }

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
@@ -36,8 +37,8 @@ final class LDefCall extends ALink implements IDefLink {
     final String name;
     final List<AExpression> arguments;
 
-    LDefCall(int line, int offset, String location, String name, List<AExpression> arguments) {
-        super(line, offset, location, -1);
+    LDefCall(Location location, String name, List<AExpression> arguments) {
+        super(location, -1);
 
         this.name = name;
         this.arguments = arguments;
@@ -67,7 +68,7 @@ final class LDefCall extends ALink implements IDefLink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         StringBuilder signature = new StringBuilder();
 
         signature.append('(');
@@ -88,6 +89,6 @@ final class LDefCall extends ALink implements IDefLink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.DefBootstrap;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Type;
@@ -34,8 +35,8 @@ final class LDefField extends ALink implements IDefLink {
 
     final String value;
 
-    LDefField(int line, int offset, String location, String value) {
-        super(line, offset, location, 1);
+    LDefField(Location location, String value) {
+        super(location, 1);
 
         this.value = value;
     }
@@ -55,14 +56,14 @@ final class LDefField extends ALink implements IDefLink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
         writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.LOAD);
     }
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
         writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.STORE);
     }

+ 14 - 13
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LField.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Field;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Struct;
@@ -38,8 +39,8 @@ public final class LField extends ALink {
 
     Field field;
 
-    public LField(int line, int offset, String location, String value) {
-        super(line, offset, location, 1);
+    public LField(Location location, String value) {
+        super(location, 1);
 
         this.value = value;
     }
@@ -47,15 +48,15 @@ public final class LField extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before == null) {
-            throw new IllegalArgumentException(error("Illegal field [" + value + "] access made without target."));
+            throw createError(new IllegalArgumentException("Illegal field [" + value + "] access made without target."));
         }
 
         Sort sort = before.sort;
 
         if (sort == Sort.ARRAY) {
-            return new LArrayLength(line, offset, location, value).copy(this).analyze(variables);
+            return new LArrayLength(location, value).copy(this).analyze(variables);
         } else if (sort == Sort.DEF) {
-            return new LDefField(line, offset, location, value).copy(this).analyze(variables);
+            return new LDefField(location, value).copy(this).analyze(variables);
         }
 
         Struct struct = before.struct;
@@ -63,7 +64,7 @@ public final class LField extends ALink {
 
         if (field != null) {
             if (store && java.lang.reflect.Modifier.isFinal(field.modifiers)) {
-                throw new IllegalArgumentException(error(
+                throw createError(new IllegalArgumentException(
                     "Cannot write to read-only field [" + value + "] for type [" + struct.name + "]."));
             }
 
@@ -80,22 +81,22 @@ public final class LField extends ALink {
                     Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
 
             if (shortcut) {
-                return new LShortcut(line, offset, location, value).copy(this).analyze(variables);
+                return new LShortcut(location, value).copy(this).analyze(variables);
             } else {
-                EConstant index = new EConstant(line, offset, location, value);
+                EConstant index = new EConstant(location, value);
                 index.analyze(variables);
 
                 if (Map.class.isAssignableFrom(before.clazz)) {
-                    return new LMapShortcut(line, offset, location, index).copy(this).analyze(variables);
+                    return new LMapShortcut(location, index).copy(this).analyze(variables);
                 }
 
                 if (List.class.isAssignableFrom(before.clazz)) {
-                    return new LListShortcut(line, offset, location, index).copy(this).analyze(variables);
+                    return new LListShortcut(location, index).copy(this).analyze(variables);
                 }
             }
         }
 
-        throw new IllegalArgumentException(error("Unknown field [" + value + "] for type [" + struct.name + "]."));
+        throw createError(new IllegalArgumentException("Unknown field [" + value + "] for type [" + struct.name + "]."));
     }
 
     @Override
@@ -105,7 +106,7 @@ public final class LField extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
             writer.getStatic(field.owner.type, field.javaName, field.type.type);
         } else {
@@ -115,7 +116,7 @@ public final class LField extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
             writer.putStatic(field.owner.type, field.javaName, field.type.type);
         } else {

+ 9 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LListShortcut.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Method;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Variables;
@@ -34,8 +35,8 @@ final class LListShortcut extends ALink {
     Method getter;
     Method setter;
 
-    LListShortcut(int line, int offset, String location, AExpression index) {
-        super(line, offset, location, 2);
+    LListShortcut(Location location, AExpression index) {
+        super(location, 2);
 
         this.index = index;
     }
@@ -47,16 +48,16 @@ final class LListShortcut extends ALink {
 
         if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1 ||
             getter.arguments.get(0).sort != Sort.INT)) {
-            throw new IllegalArgumentException(error("Illegal list get shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal list get shortcut for type [" + before.name + "]."));
         }
 
         if (setter != null && (setter.arguments.size() != 2 || setter.arguments.get(0).sort != Sort.INT)) {
-            throw new IllegalArgumentException(error("Illegal list set shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal list set shortcut for type [" + before.name + "]."));
         }
 
         if (getter != null && setter != null && (!getter.arguments.get(0).equals(setter.arguments.get(0))
             || !getter.rtn.equals(setter.arguments.get(1)))) {
-            throw new IllegalArgumentException(error("Shortcut argument types must match."));
+            throw createError(new IllegalArgumentException("Shortcut argument types must match."));
         }
 
         if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
@@ -66,7 +67,7 @@ final class LListShortcut extends ALink {
 
             after = setter != null ? setter.arguments.get(1) : getter.rtn;
         } else {
-            throw new IllegalArgumentException(error("Illegal list shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal list shortcut for type [" + before.name + "]."));
         }
 
         return this;
@@ -79,7 +80,7 @@ final class LListShortcut extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
             writer.invokeInterface(getter.owner.type, getter.method);
         } else {
@@ -93,7 +94,7 @@ final class LListShortcut extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
             writer.invokeInterface(setter.owner.type, setter.method);
         } else {

+ 9 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LMapShortcut.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Method;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Variables;
@@ -34,8 +35,8 @@ final class LMapShortcut extends ALink {
     Method getter;
     Method setter;
 
-    LMapShortcut(int line, int offset, String location, AExpression index) {
-        super(line, offset, location, 2);
+    LMapShortcut(Location location, AExpression index) {
+        super(location, 2);
 
         this.index = index;
     }
@@ -46,16 +47,16 @@ final class LMapShortcut extends ALink {
         setter = before.struct.methods.get(new Definition.MethodKey("put", 2));
 
         if (getter != null && (getter.rtn.sort == Sort.VOID || getter.arguments.size() != 1)) {
-            throw new IllegalArgumentException(error("Illegal map get shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal map get shortcut for type [" + before.name + "]."));
         }
 
         if (setter != null && setter.arguments.size() != 2) {
-            throw new IllegalArgumentException(error("Illegal map set shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal map set shortcut for type [" + before.name + "]."));
         }
 
         if (getter != null && setter != null &&
             (!getter.arguments.get(0).equals(setter.arguments.get(0)) || !getter.rtn.equals(setter.arguments.get(1)))) {
-            throw new IllegalArgumentException(error("Shortcut argument types must match."));
+            throw createError(new IllegalArgumentException("Shortcut argument types must match."));
         }
 
         if ((load || store) && (!load || getter != null) && (!store || setter != null)) {
@@ -65,7 +66,7 @@ final class LMapShortcut extends ALink {
 
             after = setter != null ? setter.arguments.get(1) : getter.rtn;
         } else {
-            throw new IllegalArgumentException(error("Illegal map shortcut for type [" + before.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal map shortcut for type [" + before.name + "]."));
         }
 
         return this;
@@ -78,7 +79,7 @@ final class LMapShortcut extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
             writer.invokeInterface(getter.owner.type, getter.method);
         } else {
@@ -92,7 +93,7 @@ final class LMapShortcut extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
             writer.invokeInterface(setter.owner.type, setter.method);
         } else {

+ 9 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewArray.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Type;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
@@ -34,8 +35,8 @@ public final class LNewArray extends ALink {
     final String type;
     final List<AExpression> arguments;
 
-    public LNewArray(int line, int offset, String location, String type, List<AExpression> arguments) {
-        super(line, offset, location, -1);
+    public LNewArray(Location location, String type, List<AExpression> arguments) {
+        super(location, -1);
 
         this.type = type;
         this.arguments = arguments;
@@ -44,11 +45,11 @@ public final class LNewArray extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before != null) {
-            throw new IllegalArgumentException(error("Cannot create a new array with a target already defined."));
+            throw createError(new IllegalArgumentException("Cannot create a new array with a target already defined."));
         } else if (store) {
-            throw new IllegalArgumentException(error("Cannot assign a value to a new array."));
+            throw createError(new IllegalArgumentException("Cannot assign a value to a new array."));
         } else if (!load) {
-            throw new IllegalArgumentException(error("A newly created array must be read."));
+            throw createError(new IllegalArgumentException("A newly created array must be read."));
         }
 
         final Type type;
@@ -56,7 +57,7 @@ public final class LNewArray extends ALink {
         try {
             type = Definition.getType(this.type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
         for (int argument = 0; argument < arguments.size(); ++argument) {
@@ -79,7 +80,7 @@ public final class LNewArray extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         for (AExpression argument : arguments) {
             argument.write(writer);
         }
@@ -93,6 +94,6 @@ public final class LNewArray extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 11 - 10
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LNewObj.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Constructor;
 import org.elasticsearch.painless.Definition.Struct;
 import org.elasticsearch.painless.Definition.Type;
@@ -29,7 +30,7 @@ import org.elasticsearch.painless.MethodWriter;
 import java.util.List;
 
 /**
- * Respresents and object instantiation.
+ * Represents and object instantiation.
  */
 public final class LNewObj extends ALink {
 
@@ -38,8 +39,8 @@ public final class LNewObj extends ALink {
 
     Constructor constructor;
 
-    public LNewObj(int line, int offset, String location, String type, List<AExpression> arguments) {
-        super(line, offset, location, -1);
+    public LNewObj(Location location, String type, List<AExpression> arguments) {
+        super(location, -1);
 
         this.type = type;
         this.arguments = arguments;
@@ -48,9 +49,9 @@ public final class LNewObj extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before != null) {
-            throw new IllegalArgumentException(error("Illegal new call with a target already defined."));
+            throw createError(new IllegalArgumentException("Illegal new call with a target already defined."));
         } else if (store) {
-            throw new IllegalArgumentException(error("Cannot assign a value to a new call."));
+            throw createError(new IllegalArgumentException("Cannot assign a value to a new call."));
         }
 
         final Type type;
@@ -58,7 +59,7 @@ public final class LNewObj extends ALink {
         try {
             type = Definition.getType(this.type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
         Struct struct = type.struct;
@@ -69,7 +70,7 @@ public final class LNewObj extends ALink {
             constructor.arguments.toArray(types);
 
             if (constructor.arguments.size() != arguments.size()) {
-                throw new IllegalArgumentException(error("When calling constructor on type [" + struct.name + "]" +
+                throw createError(new IllegalArgumentException("When calling constructor on type [" + struct.name + "]" +
                     " expected [" + constructor.arguments.size() + "] arguments, but found [" + arguments.size() + "]."));
             }
 
@@ -85,7 +86,7 @@ public final class LNewObj extends ALink {
             statement = true;
             after = type;
         } else {
-            throw new IllegalArgumentException(error("Unknown new call on type [" + struct.name + "]."));
+            throw createError(new IllegalArgumentException("Unknown new call on type [" + struct.name + "]."));
         }
 
         return this;
@@ -98,7 +99,7 @@ public final class LNewObj extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         writer.newInstance(after.type);
 
         if (load) {
@@ -114,6 +115,6 @@ public final class LNewObj extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 9 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LShortcut.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Method;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Definition.Struct;
@@ -36,8 +37,8 @@ final class LShortcut extends ALink {
     Method getter = null;
     Method setter = null;
 
-    LShortcut(int line, int offset, String location, String value) {
-        super(line, offset, location, 1);
+    LShortcut(Location location, String value) {
+        super(location, 1);
 
         this.value = value;
     }
@@ -55,23 +56,23 @@ final class LShortcut extends ALink {
         setter = struct.methods.get(new Definition.MethodKey("set" + Character.toUpperCase(value.charAt(0)) + value.substring(1), 1));
 
         if (getter != null && (getter.rtn.sort == Sort.VOID || !getter.arguments.isEmpty())) {
-            throw new IllegalArgumentException(error(
+            throw createError(new IllegalArgumentException(
                 "Illegal get shortcut on field [" + value + "] for type [" + struct.name + "]."));
         }
 
         if (setter != null && (setter.rtn.sort != Sort.VOID || setter.arguments.size() != 1)) {
-            throw new IllegalArgumentException(error(
+            throw createError(new IllegalArgumentException(
                 "Illegal set shortcut on field [" + value + "] for type [" + struct.name + "]."));
         }
 
         if (getter != null && setter != null && setter.arguments.get(0) != getter.rtn) {
-            throw new IllegalArgumentException(error("Shortcut argument types must match."));
+            throw createError(new IllegalArgumentException("Shortcut argument types must match."));
         }
 
         if ((getter != null || setter != null) && (!load || getter != null) && (!store || setter != null)) {
             after = setter != null ? setter.arguments.get(0) : getter.rtn;
         } else {
-            throw new IllegalArgumentException(error("Illegal shortcut on field [" + value + "] for type [" + struct.name + "]."));
+            throw createError(new IllegalArgumentException("Illegal shortcut on field [" + value + "] for type [" + struct.name + "]."));
         }
 
         return this;
@@ -84,7 +85,7 @@ final class LShortcut extends ALink {
 
     @Override
     void load(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
             writer.invokeInterface(getter.owner.type, getter.method);
         } else {
@@ -98,7 +99,7 @@ final class LShortcut extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        writer.writeDebugInfo(offset);
+        writer.writeDebugInfo(location);
         if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
             writer.invokeInterface(setter.owner.type, setter.method);
         } else {

+ 8 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LStatic.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Variables;
 
@@ -30,8 +31,8 @@ public final class LStatic extends ALink {
 
     final String type;
 
-    public LStatic(int line, int offset, String location, String type) {
-        super(line, offset, location, 0);
+    public LStatic(Location location, String type) {
+        super(location, 0);
 
         this.type = type;
     }
@@ -39,14 +40,14 @@ public final class LStatic extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before != null) {
-            throw new IllegalArgumentException(error("Illegal static type [" + type + "] after target already defined."));
+            throw createError(new IllegalArgumentException("Illegal static type [" + type + "] after target already defined."));
         }
 
         try {
             after = Definition.getType(type);
             statik = true;
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + type + "]."));
         }
 
         return this;
@@ -54,16 +55,16 @@ public final class LStatic extends ALink {
 
     @Override
     void write(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
     void load(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

+ 7 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/LString.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -28,8 +29,8 @@ import org.elasticsearch.painless.MethodWriter;
  */
 public final class LString extends ALink {
 
-    public LString(int line, int offset, String location, String string) {
-        super(line, offset, location, -1);
+    public LString(Location location, String string) {
+        super(location, -1);
 
         this.string = string;
     }
@@ -37,11 +38,11 @@ public final class LString extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before != null) {
-            throw new IllegalArgumentException(error("Illegal String constant [" + string + "]."));
+            throw createError(new IllegalArgumentException("Illegal String constant [" + string + "]."));
         } else if (store) {
-            throw new IllegalArgumentException(error("Cannot write to read-only String constant [" + string + "]."));
+            throw createError(new IllegalArgumentException("Cannot write to read-only String constant [" + string + "]."));
         } else if (!load) {
-            throw new IllegalArgumentException(error("Must read String constant [" + string + "]."));
+            throw createError(new IllegalArgumentException("Must read String constant [" + string + "]."));
         }
 
         after = Definition.STRING_TYPE;
@@ -61,6 +62,6 @@ public final class LString extends ALink {
 
     @Override
     void store(MethodWriter writer) {
-        throw new IllegalStateException(error("Illegal tree structure."));
+        throw createError(new IllegalStateException("Illegal tree structure."));
     }
 }

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

@@ -19,6 +19,7 @@
 
 package org.elasticsearch.painless.node;
 
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.Variables.Variable;
@@ -33,8 +34,8 @@ public final class LVariable extends ALink {
 
     int slot;
 
-    public LVariable(int line, int offset, String location, String name) {
-        super(line, offset, location, 0);
+    public LVariable(Location location, String name) {
+        super(location, 0);
 
         this.name = name;
     }
@@ -42,13 +43,13 @@ public final class LVariable extends ALink {
     @Override
     ALink analyze(Variables variables) {
         if (before != null) {
-            throw new IllegalArgumentException(error("Illegal variable [" + name + "] access with target already defined."));
+            throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
         }
 
         Variable variable = variables.getVariable(location, name);
 
         if (store && variable.readonly) {
-            throw new IllegalArgumentException(error("Variable [" + variable.name + "] is read-only."));
+            throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));
         }
 
         slot = variable.slot;

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 import java.util.Collections;
@@ -32,8 +33,8 @@ public final class SBlock extends AStatement {
 
     final List<AStatement> statements;
 
-    public SBlock(int line, int offset, String location, List<AStatement> statements) {
-        super(line, offset, location);
+    public SBlock(Location location, List<AStatement> statements) {
+        super(location);
 
         this.statements = Collections.unmodifiableList(statements);
     }
@@ -41,14 +42,14 @@ public final class SBlock extends AStatement {
     @Override
     void analyze(Variables variables) {
         if (statements == null || statements.isEmpty()) {
-            throw new IllegalArgumentException(error("A block must contain at least one statement."));
+            throw createError(new IllegalArgumentException("A block must contain at least one statement."));
         }
 
         final AStatement last = statements.get(statements.size() - 1);
 
         for (AStatement statement : statements) {
             if (allEscape) {
-                throw new IllegalArgumentException(error("Unreachable statement."));
+                throw createError(new IllegalArgumentException("Unreachable statement."));
             }
 
             statement.inLoop = inLoop;

+ 4 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SBreak.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 /**
@@ -27,14 +28,14 @@ import org.elasticsearch.painless.MethodWriter;
  */
 public final class SBreak extends AStatement {
 
-    public SBreak(int line, int offset, String location) {
-        super(line, offset, location);
+    public SBreak(Location location) {
+        super(location);
     }
 
     @Override
     void analyze(Variables variables) {
         if (!inLoop) {
-            throw new IllegalArgumentException(error("Break statement outside of a loop."));
+            throw createError(new IllegalArgumentException("Break statement outside of a loop."));
         }
 
         loopEscape = true;

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SCatch.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Type;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.Variables.Variable;
@@ -42,8 +43,8 @@ public final class SCatch extends AStatement {
     Label end;
     Label exception;
 
-    public SCatch(int line, int offset, String location, String type, String name, SBlock block) {
-        super(line, offset, location);
+    public SCatch(Location location, String type, String name, SBlock block) {
+        super(location);
 
         this.type = type;
         this.name = name;
@@ -57,11 +58,11 @@ public final class SCatch extends AStatement {
         try {
             type = Definition.getType(this.type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
         if (!Exception.class.isAssignableFrom(type.clazz)) {
-            throw new ClassCastException(error("Not an exception type [" + this.type + "]."));
+            throw createError(new ClassCastException("Not an exception type [" + this.type + "]."));
         }
 
         variable = variables.addVariable(location, type, name, true, false);
@@ -84,7 +85,7 @@ public final class SCatch extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label jump = new Label();
 
         writer.mark(jump);

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 /**
@@ -27,18 +28,18 @@ import org.elasticsearch.painless.MethodWriter;
  */
 public final class SContinue extends AStatement {
 
-    public SContinue(int line, int offset, String location) {
-        super(line, offset, location);
+    public SContinue(Location location) {
+        super(location);
     }
 
     @Override
     void analyze(Variables variables) {
         if (!inLoop) {
-            throw new IllegalArgumentException(error("Continue statement outside of a loop."));
+            throw createError(new IllegalArgumentException("Continue statement outside of a loop."));
         }
 
         if (lastLoop) {
-            throw new IllegalArgumentException(error("Extraneous continue statement."));
+            throw createError(new IllegalArgumentException("Extraneous continue statement."));
         }
 
         allEscape = true;

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 import java.util.Collections;
@@ -32,8 +33,8 @@ public final class SDeclBlock extends AStatement {
 
     final List<SDeclaration> declarations;
 
-    public SDeclBlock(int line, int offset, String location, List<SDeclaration> declarations) {
-        super(line, offset, location);
+    public SDeclBlock(Location location, List<SDeclaration> declarations) {
+        super(location);
 
         this.declarations = Collections.unmodifiableList(declarations);
     }

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDeclaration.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Type;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.Variables.Variable;
@@ -37,8 +38,8 @@ public final class SDeclaration extends AStatement {
 
     Variable variable;
 
-    public SDeclaration(int line, int offset, String location, String type, String name, AExpression expression) {
-        super(line, offset, location);
+    public SDeclaration(Location location, String type, String name, AExpression expression) {
+        super(location);
 
         this.type = type;
         this.name = name;
@@ -52,7 +53,7 @@ public final class SDeclaration extends AStatement {
         try {
             type = Definition.getType(this.type);
         } catch (IllegalArgumentException exception) {
-            throw new IllegalArgumentException(error("Not a type [" + this.type + "]."));
+            throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
         }
 
         if (expression != null) {
@@ -66,10 +67,10 @@ public final class SDeclaration extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         if (expression == null) {
             switch (variable.type.sort) {
-                case VOID:   throw new IllegalStateException(error("Illegal tree structure."));
+                case VOID:   throw createError(new IllegalStateException("Illegal tree structure."));
                 case BOOL:
                 case BYTE:
                 case SHORT:

+ 8 - 7
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SDo.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
 import org.elasticsearch.painless.MethodWriter;
@@ -33,8 +34,8 @@ public final class SDo extends AStatement {
     final SBlock block;
     AExpression condition;
 
-    public SDo(int line, int offset, String location, int maxLoopCounter, SBlock block, AExpression condition) {
-        super(line, offset, location);
+    public SDo(Location location, int maxLoopCounter, SBlock block, AExpression condition) {
+        super(location);
 
         this.condition = condition;
         this.block = block;
@@ -46,7 +47,7 @@ public final class SDo extends AStatement {
         variables.incrementScope();
 
         if (block == null) {
-            throw new IllegalArgumentException(error("Extraneous do while loop."));
+            throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
         block.beginLoop = true;
@@ -55,7 +56,7 @@ public final class SDo extends AStatement {
         block.analyze(variables);
 
         if (block.loopEscape && !block.anyContinue) {
-            throw new IllegalArgumentException(error("Extraneous do while loop."));
+            throw createError(new IllegalArgumentException("Extraneous do while loop."));
         }
 
         condition.expected = Definition.BOOLEAN_TYPE;
@@ -66,7 +67,7 @@ public final class SDo extends AStatement {
             final boolean continuous = (boolean)condition.constant;
 
             if (!continuous) {
-                throw new IllegalArgumentException(error("Extraneous do while loop."));
+                throw createError(new IllegalArgumentException("Extraneous do while loop."));
             }
 
             if (!block.anyBreak) {
@@ -86,7 +87,7 @@ public final class SDo extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label start = new Label();
         Label begin = new Label();
         Label end = new Label();
@@ -102,7 +103,7 @@ public final class SDo extends AStatement {
         condition.fals = end;
         condition.write(writer);
 
-        writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), offset);
+        writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location);
 
         writer.goTo(start);
         writer.mark(end);

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Definition.Sort;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
@@ -31,8 +32,8 @@ public final class SExpression extends AStatement {
 
     AExpression expression;
 
-    public SExpression(int line, int offset, String location, AExpression expression) {
-        super(line, offset, location);
+    public SExpression(Location location, AExpression expression) {
+        super(location);
 
         this.expression = expression;
     }
@@ -43,7 +44,7 @@ public final class SExpression extends AStatement {
         expression.analyze(variables);
 
         if (!lastSource && !expression.statement) {
-            throw new IllegalArgumentException(error("Not a statement."));
+            throw createError(new IllegalArgumentException("Not a statement."));
         }
 
         final boolean rtn = lastSource && expression.actual.sort != Sort.VOID;
@@ -60,7 +61,7 @@ public final class SExpression extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         expression.write(writer);
 
         if (methodEscape) {

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

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
 import org.elasticsearch.painless.MethodWriter;
@@ -35,9 +36,9 @@ public final class SFor extends AStatement {
     AExpression afterthought;
     final SBlock block;
 
-    public SFor(int line, int offset, String location, int maxLoopCounter,
+    public SFor(Location location, int maxLoopCounter,
                 ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
-        super(line, offset, location);
+        super(location);
 
         this.initializer = initializer;
         this.condition = condition;
@@ -62,10 +63,10 @@ public final class SFor extends AStatement {
                 initializer.analyze(variables);
 
                 if (!initializer.statement) {
-                    throw new IllegalArgumentException(initializer.error("Not a statement."));
+                    throw createError(new IllegalArgumentException("Not a statement."));
                 }
             } else {
-                throw new IllegalStateException(error("Illegal tree structure."));
+                throw createError(new IllegalStateException("Illegal tree structure."));
             }
         }
 
@@ -78,11 +79,11 @@ public final class SFor extends AStatement {
                 continuous = (boolean)condition.constant;
 
                 if (!continuous) {
-                    throw new IllegalArgumentException(error("Extraneous for loop."));
+                    throw createError(new IllegalArgumentException("Extraneous for loop."));
                 }
 
                 if (block == null) {
-                    throw new IllegalArgumentException(error("For loop has no escape."));
+                    throw createError(new IllegalArgumentException("For loop has no escape."));
                 }
             }
         } else {
@@ -94,7 +95,7 @@ public final class SFor extends AStatement {
             afterthought.analyze(variables);
 
             if (!afterthought.statement) {
-                throw new IllegalArgumentException(afterthought.error("Not a statement."));
+                throw createError(new IllegalArgumentException("Not a statement."));
             }
         }
 
@@ -105,7 +106,7 @@ public final class SFor extends AStatement {
             block.analyze(variables);
 
             if (block.loopEscape && !block.anyContinue) {
-                throw new IllegalArgumentException(error("Extraneous for loop."));
+                throw createError(new IllegalArgumentException("Extraneous for loop."));
             }
 
             if (continuous && !block.anyBreak) {
@@ -127,7 +128,7 @@ public final class SFor extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label start = new Label();
         Label begin = afterthought == null ? start : new Label();
         Label end = new Label();
@@ -159,10 +160,10 @@ public final class SFor extends AStatement {
                 ++statementCount;
             }
 
-            writer.writeLoopCounter(loopCounterSlot, statementCount, offset);
+            writer.writeLoopCounter(loopCounterSlot, statementCount, location);
             block.write(writer);
         } else {
-            writer.writeLoopCounter(loopCounterSlot, 1, offset);
+            writer.writeLoopCounter(loopCounterSlot, 1, location);
         }
 
         if (afterthought != null) {

+ 6 - 5
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIf.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
 import org.elasticsearch.painless.MethodWriter;
@@ -32,8 +33,8 @@ public final class SIf extends AStatement {
     AExpression condition;
     final SBlock ifblock;
 
-    public SIf(int line, int offset, String location, AExpression condition, SBlock ifblock) {
-        super(line, offset, location);
+    public SIf(Location location, AExpression condition, SBlock ifblock) {
+        super(location);
 
         this.condition = condition;
         this.ifblock = ifblock;
@@ -46,11 +47,11 @@ public final class SIf extends AStatement {
         condition = condition.cast(variables);
 
         if (condition.constant != null) {
-            throw new IllegalArgumentException(error("Extraneous if statement."));
+            throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
         if (ifblock == null) {
-            throw new IllegalArgumentException(error("Extraneous if statement."));
+            throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
         ifblock.lastSource = lastSource;
@@ -68,7 +69,7 @@ public final class SIf extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label fals = new Label();
 
         condition.fals = fals;

+ 7 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SIfElse.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
 import org.elasticsearch.painless.MethodWriter;
@@ -33,8 +34,8 @@ public final class SIfElse extends AStatement {
     final SBlock ifblock;
     final SBlock elseblock;
 
-    public SIfElse(int line, int offset, String location, AExpression condition, SBlock ifblock, SBlock elseblock) {
-        super(line, offset, location);
+    public SIfElse(Location location, AExpression condition, SBlock ifblock, SBlock elseblock) {
+        super(location);
 
         this.condition = condition;
         this.ifblock = ifblock;
@@ -48,11 +49,11 @@ public final class SIfElse extends AStatement {
         condition = condition.cast(variables);
 
         if (condition.constant != null) {
-            throw new IllegalArgumentException(error("Extraneous if statement."));
+            throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
         if (ifblock == null) {
-            throw new IllegalArgumentException(error("Extraneous if statement."));
+            throw createError(new IllegalArgumentException("Extraneous if statement."));
         }
 
         ifblock.lastSource = lastSource;
@@ -68,7 +69,7 @@ public final class SIfElse extends AStatement {
         statementCount = ifblock.statementCount;
 
         if (elseblock == null) {
-            throw new IllegalArgumentException(error("Extraneous else statement."));
+            throw createError(new IllegalArgumentException("Extraneous else statement."));
         }
 
         elseblock.lastSource = lastSource;
@@ -89,7 +90,7 @@ public final class SIfElse extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label end = new Label();
         Label fals = elseblock != null ? new Label() : end;
 

+ 4 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SReturn.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -30,8 +31,8 @@ public final class SReturn extends AStatement {
 
     AExpression expression;
 
-    public SReturn(int line, int offset, String location, AExpression expression) {
-        super(line, offset, location);
+    public SReturn(Location location, AExpression expression) {
+        super(location);
 
         this.expression = expression;
     }
@@ -52,7 +53,7 @@ public final class SReturn extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         expression.write(writer);
         writer.returnValue();
     }

+ 6 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SSource.java

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Opcodes;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 import java.util.Collections;
@@ -33,8 +34,8 @@ public final class SSource extends AStatement {
 
     final List<AStatement> statements;
 
-    public SSource(int line, int offset, String location, List<AStatement> statements) {
-        super(line, offset, location);
+    public SSource(Location location, List<AStatement> statements) {
+        super(location);
 
         this.statements = Collections.unmodifiableList(statements);
     }
@@ -42,7 +43,7 @@ public final class SSource extends AStatement {
     @Override
     public void analyze(Variables variables) {
         if (statements == null || statements.isEmpty()) {
-            throw new IllegalArgumentException(error("Cannot generate an empty script."));
+            throw createError(new IllegalArgumentException("Cannot generate an empty script."));
         }
 
         variables.incrementScope();
@@ -50,8 +51,9 @@ public final class SSource extends AStatement {
         final AStatement last = statements.get(statements.size() - 1);
 
         for (AStatement statement : statements) {
+            // TODO: why are we checking only statements 0..n-1 (this effectively checks only the previous statement)
             if (allEscape) {
-                throw new IllegalArgumentException(error("Unreachable statement."));
+                throw createError(new IllegalArgumentException("Unreachable statement."));
             }
 
             statement.lastSource = statement == last;

+ 4 - 3
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SThrow.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.elasticsearch.painless.MethodWriter;
 
@@ -30,8 +31,8 @@ public final class SThrow extends AStatement {
 
     AExpression expression;
 
-    public SThrow(int line, int offset, String location, AExpression expression) {
-        super(line, offset, location);
+    public SThrow(Location location, AExpression expression) {
+        super(location);
 
         this.expression = expression;
     }
@@ -50,7 +51,7 @@ public final class SThrow extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         expression.write(writer);
         writer.throwException();
     }

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

@@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 
 import java.util.Collections;
@@ -34,8 +35,8 @@ public final class STry extends AStatement {
     final SBlock block;
     final List<SCatch> catches;
 
-    public STry(int line, int offset, String location, SBlock block, List<SCatch> traps) {
-        super(line, offset, location);
+    public STry(Location location, SBlock block, List<SCatch> traps) {
+        super(location);
 
         this.block = block;
         this.catches = Collections.unmodifiableList(traps);
@@ -44,7 +45,7 @@ public final class STry extends AStatement {
     @Override
     void analyze(Variables variables) {
         if (block == null) {
-            throw new IllegalArgumentException(error("Extraneous try statement."));
+            throw createError(new IllegalArgumentException("Extraneous try statement."));
         }
 
         block.lastSource = lastSource;
@@ -86,7 +87,7 @@ public final class STry extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label begin = new Label();
         Label end = new Label();
         Label exception = new Label();

+ 9 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/SWhile.java

@@ -20,6 +20,7 @@
 package org.elasticsearch.painless.node;
 
 import org.elasticsearch.painless.Definition;
+import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.Variables;
 import org.objectweb.asm.Label;
 import org.elasticsearch.painless.MethodWriter;
@@ -33,8 +34,8 @@ public final class SWhile extends AStatement {
     AExpression condition;
     final SBlock block;
 
-    public SWhile(int line, int offset, String location, int maxLoopCounter, AExpression condition, SBlock block) {
-        super(line, offset, location);
+    public SWhile(Location location, int maxLoopCounter, AExpression condition, SBlock block) {
+        super(location);
 
         this.maxLoopCounter = maxLoopCounter;
         this.condition = condition;
@@ -55,11 +56,11 @@ public final class SWhile extends AStatement {
             continuous = (boolean)condition.constant;
 
             if (!continuous) {
-                throw new IllegalArgumentException(error("Extraneous while loop."));
+                throw createError(new IllegalArgumentException("Extraneous while loop."));
             }
 
             if (block == null) {
-                throw new IllegalArgumentException(error("While loop has no escape."));
+                throw createError(new IllegalArgumentException("While loop has no escape."));
             }
         }
 
@@ -70,7 +71,7 @@ public final class SWhile extends AStatement {
             block.analyze(variables);
 
             if (block.loopEscape && !block.anyContinue) {
-                throw new IllegalArgumentException(error("Extraneous while loop."));
+                throw createError(new IllegalArgumentException("Extraneous while loop."));
             }
 
             if (continuous && !block.anyBreak) {
@@ -92,7 +93,7 @@ public final class SWhile extends AStatement {
 
     @Override
     void write(MethodWriter writer) {
-        writer.writeStatementOffset(offset);
+        writer.writeStatementOffset(location);
         Label begin = new Label();
         Label end = new Label();
 
@@ -102,13 +103,13 @@ public final class SWhile extends AStatement {
         condition.write(writer);
 
         if (block != null) {
-            writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), offset);
+            writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location);
 
             block.continu = begin;
             block.brake = end;
             block.write(writer);
         } else {
-            writer.writeLoopCounter(loopCounterSlot, 1, offset);
+            writer.writeLoopCounter(loopCounterSlot, 1, location);
         }
 
         if (block == null || !block.allEscape) {

+ 8 - 8
modules/lang-painless/src/main/java/org/elasticsearch/painless/node/package-info.java

@@ -24,8 +24,8 @@
  * A* (abstract) - These are the abstract nodes that are the superclasses for the other types.
  * I* (interface) -- Thse are marker interfaces to denote a property of the node.
  * S* (statement) - These are nodes that represent a statement in Painless.  These are the highest level nodes.
- * E* (expression) - These are nodess that represent an expression in Painless.  These are the middle level nodes.
- * L* (link) - These are nodes that respresent a piece of a variable/method chain.  The are the lowest level nodes.
+ * E* (expression) - These are nodes that represent an expression in Painless.  These are the middle level nodes.
+ * L* (link) - These are nodes that represent a piece of a variable/method chain.  The are the lowest level nodes.
  * <p>
  * The following is a brief description of each node:
  * {@link org.elasticsearch.painless.node.AExpression} - The superclass for all E* (expression) nodes.
@@ -38,18 +38,18 @@
  * {@link org.elasticsearch.painless.node.ECast} - Represents an implicit cast in most cases.  (Internal only.)
  * {@link org.elasticsearch.painless.node.EChain} - Represents the entirety of a variable/method chain for read/write operations.
  * {@link org.elasticsearch.painless.node.EComp} - Represents a comparison expression.
- * {@link org.elasticsearch.painless.node.EConditional} - Respresents a conditional expression.
- * {@link org.elasticsearch.painless.node.EConstant} - Respresents a constant.  (Internal only.)
- * {@link org.elasticsearch.painless.node.EDecimal} - Respresents a decimal constant.
+ * {@link org.elasticsearch.painless.node.EConditional} - Represents a conditional expression.
+ * {@link org.elasticsearch.painless.node.EConstant} - Represents a constant.  (Internal only.)
+ * {@link org.elasticsearch.painless.node.EDecimal} - Represents a decimal constant.
  * {@link org.elasticsearch.painless.node.EExplicit} - Represents an explicit cast.
  * {@link org.elasticsearch.painless.node.EFunctionRef} - Represents a function reference.
  * {@link org.elasticsearch.painless.node.ENull} - Represents a null constant.
- * {@link org.elasticsearch.painless.node.ENumeric} - Respresents a non-decimal numeric constant.
+ * {@link org.elasticsearch.painless.node.ENumeric} - Represents a non-decimal numeric constant.
  * {@link org.elasticsearch.painless.node.EUnary} - Represents a unary math expression.
  * {@link org.elasticsearch.painless.node.IDefLink} - A marker interface for all LDef* (link) nodes.
  * {@link org.elasticsearch.painless.node.LArrayLength} - Represents an array length field load.
  * {@link org.elasticsearch.painless.node.LBrace} - Represents an array load/store or defers to possible shortcuts.
- * {@link org.elasticsearch.painless.node.LCall} - Represents a method call or deferes to a def call.
+ * {@link org.elasticsearch.painless.node.LCall} - Represents a method call or defers to a def call.
  * {@link org.elasticsearch.painless.node.LCast} - Represents a cast made in a variable/method chain.
  * {@link org.elasticsearch.painless.node.LDefArray} - Represents an array load/store or shortcut on a def type.  (Internal only.)
  * {@link org.elasticsearch.painless.node.LDefCall} - Represents a method call made on a def type. (Internal only.)
@@ -58,7 +58,7 @@
  * {@link org.elasticsearch.painless.node.LListShortcut} - Represents a list load/store shortcut.  (Internal only.)
  * {@link org.elasticsearch.painless.node.LMapShortcut} - Represents a map load/store shortcut. (Internal only.)
  * {@link org.elasticsearch.painless.node.LNewArray} - Represents an array instantiation.
- * {@link org.elasticsearch.painless.node.LNewObj} - Respresents and object instantiation.
+ * {@link org.elasticsearch.painless.node.LNewObj} - Represents and object instantiation.
  * {@link org.elasticsearch.painless.node.LShortcut} - Represents a field load/store shortcut.  (Internal only.)
  * {@link org.elasticsearch.painless.node.LStatic} - Represents a static type target.
  * {@link org.elasticsearch.painless.node.LString} - Represents a string constant.

+ 8 - 12
modules/lang-painless/src/test/java/org/elasticsearch/painless/ConditionalTests.java

@@ -70,24 +70,20 @@ public class ConditionalTests extends ScriptTestCase {
     }
 
     public void testIncompatibleAssignment() {
-        try {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("boolean x = false; byte z = x ? 2 : 4.0F; return z;");
-            fail("expected class cast exception");
-        } catch (ClassCastException expected) {}
+        });
 
-        try {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("boolean x = false; Map z = x ? 4 : (byte)7; return z;");
-            fail("expected class cast exception");
-        } catch (ClassCastException expected) {}
+        });
 
-        try {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("boolean x = false; Map z = x ? new HashMap() : new ArrayList(); return z;");
-            fail("expected class cast exception");
-        } catch (ClassCastException expected) {}
+        });
 
-        try {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("boolean x = false; int y = 2; byte z = x ? y : 7; return z;");
-            fail("expected class cast exception");
-        } catch (ClassCastException expected) {}
+        });
     }
 }

+ 2 - 2
modules/lang-painless/src/test/java/org/elasticsearch/painless/DivisionTests.java

@@ -124,11 +124,11 @@ public class DivisionTests extends ScriptTestCase {
     }
 
     public void testDivideByZeroConst() throws Exception {
-        expectThrows(ArithmeticException.class, () -> {
+        expectScriptThrows(ArithmeticException.class, () -> {
             exec("return 1/0;");
         });
 
-        expectThrows(ArithmeticException.class, () -> {
+        expectScriptThrows(ArithmeticException.class, () -> {
             exec("return 1L/0L;");
         });
     }

+ 1 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/FunctionRefTests.java

@@ -21,7 +21,7 @@ package org.elasticsearch.painless;
 
 public class FunctionRefTests extends ScriptTestCase {
     public void testUnsupported() {
-        expectThrows(UnsupportedOperationException.class, () -> {
+        expectScriptThrows(UnsupportedOperationException.class, () -> {
            exec("DoubleStream.Builder builder = DoubleStream.builder();" +
                "builder.add(2.0); builder.add(1.0); builder.add(3.0);" +
                "builder.build().reduce(Double::unsupported);");

+ 1 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/OverloadTests.java

@@ -25,7 +25,7 @@ public class OverloadTests extends ScriptTestCase {
     public void testMethod() {
         assertEquals(2, exec("return 'abc123abc'.indexOf('c');"));
         assertEquals(8, exec("return 'abc123abc'.indexOf('c', 3);"));
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("return 'abc123abc'.indexOf('c', 3, 'bogus');");
         });
         assertTrue(expected.getMessage().contains("[indexOf] with [3] arguments"));

+ 4 - 10
modules/lang-painless/src/test/java/org/elasticsearch/painless/RemainderTests.java

@@ -124,18 +124,12 @@ public class RemainderTests extends ScriptTestCase {
     }
 
     public void testDivideByZeroConst() throws Exception {
-        try {
+        expectScriptThrows(ArithmeticException.class, () -> {
             exec("return 1%0;");
-            fail("should have hit exception");
-        } catch (ArithmeticException expected) {
-            // divide by zero
-        }
+        });
 
-        try {
+        expectScriptThrows(ArithmeticException.class, () -> {
             exec("return 1L%0L;");
-            fail("should have hit exception");
-        } catch (ArithmeticException expected) {
-            // divide by zero
-        }
+        });
     }
 }

+ 8 - 8
modules/lang-painless/src/test/java/org/elasticsearch/painless/ReservedWordTests.java

@@ -27,7 +27,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't declare a variable of _score, its really reserved! */
     public void testScoreVar() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("int _score = 5; return _score;");
         });
         assertTrue(expected.getMessage().contains("Variable name [_score] is reserved"));
@@ -35,7 +35,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't write to _score, its read-only! */
     public void testScoreStore() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("_score = 5; return _score;");
         });
         assertTrue(expected.getMessage().contains("Variable [_score] is read-only"));
@@ -43,7 +43,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't declare a variable of doc, its really reserved! */
     public void testDocVar() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("int doc = 5; return doc;");
         });
         assertTrue(expected.getMessage().contains("Variable name [doc] is reserved"));
@@ -51,7 +51,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't write to doc, its read-only! */
     public void testDocStore() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("doc = 5; return doc;");
         });
         assertTrue(expected.getMessage().contains("Variable [doc] is read-only"));
@@ -59,7 +59,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't declare a variable of ctx, its really reserved! */
     public void testCtxVar() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("int ctx = 5; return ctx;");
         });
         assertTrue(expected.getMessage().contains("Variable name [ctx] is reserved"));
@@ -67,7 +67,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't write to ctx, its read-only! */
     public void testCtxStore() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("ctx = 5; return ctx;");
         });
         assertTrue(expected.getMessage().contains("Variable [ctx] is read-only"));
@@ -80,7 +80,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't declare a variable of _value, its really reserved! */
     public void testAggregationValueVar() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("int _value = 5; return _value;");
         });
         assertTrue(expected.getMessage().contains("Variable name [_value] is reserved"));
@@ -88,7 +88,7 @@ public class ReservedWordTests extends ScriptTestCase {
 
     /** check that we can't write to _value, its read-only! */
     public void testAggregationValueStore() {
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec("_value = 5; return _value;");
         });
         assertTrue(expected.getMessage().contains("Variable [_value] is read-only"));

+ 4 - 4
modules/lang-painless/src/test/java/org/elasticsearch/painless/WhenThingsGoWrongTests.java

@@ -71,11 +71,11 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
     }
 
     public void testInvalidShift() {
-        expectThrows(ClassCastException.class, () -> {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("float x = 15F; x <<= 2; return x;");
         });
 
-        expectThrows(ClassCastException.class, () -> {
+        expectScriptThrows(ClassCastException.class, () -> {
             exec("double x = 15F; x <<= 2; return x;");
         });
     }
@@ -134,7 +134,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
         assertTrue(expected.getMessage().contains(
                    "The maximum number of statements that can be executed in a loop has been reached."));
 
-        RuntimeException parseException = expectThrows(RuntimeException.class, () -> {
+        RuntimeException parseException = expectScriptThrows(RuntimeException.class, () -> {
             exec("try { int x; } catch (PainlessError error) {}");
             fail("should have hit ParseException");
         });
@@ -156,7 +156,7 @@ public class WhenThingsGoWrongTests extends ScriptTestCase {
         final char[] tooManyChars = new char[Compiler.MAXIMUM_SOURCE_LENGTH + 1];
         Arrays.fill(tooManyChars, '0');
 
-        IllegalArgumentException expected = expectThrows(IllegalArgumentException.class, () -> {
+        IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
             exec(new String(tooManyChars));
         });
         assertTrue(expected.getMessage().contains("Scripts may be no longer than"));

+ 2 - 2
modules/lang-painless/src/test/java/org/elasticsearch/painless/antlr/ParserTests.java

@@ -40,9 +40,9 @@ public class ParserTests extends ScriptTestCase {
 
     private SourceContext buildAntlrTree(String source) {
         ANTLRInputStream stream = new ANTLRInputStream(source);
-        PainlessLexer lexer = new ErrorHandlingLexer(stream);
+        PainlessLexer lexer = new ErrorHandlingLexer(stream, "testing");
         PainlessParser parser = new PainlessParser(new CommonTokenStream(lexer));
-        ParserErrorStrategy strategy = new ParserErrorStrategy();
+        ParserErrorStrategy strategy = new ParserErrorStrategy("testing");
 
         lexer.removeErrorListeners();
         parser.removeErrorListeners();

+ 1 - 1
modules/lang-painless/src/test/resources/rest-api-spec/test/plan_a/16_update2.yaml

@@ -47,7 +47,7 @@
         body: { "script":  "_score * foo bar + doc['myParent.weight'].value" }
 
   - do:
-      catch: /Unable.to.parse.*/
+      catch: /compile error/
       put_script:
         id: "1"
         lang: "painless"