changeset 1292:323f54e277df jdk8u60-b03

Merge
author lana
date Wed, 11 Feb 2015 18:55:44 -0800
parents 493c400c96e0 (current diff) dcd7d8d48cf5 (diff)
children b0b90d6c5265 4dee46412516
files
diffstat 63 files changed, 1900 insertions(+), 614 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/samples/getclassnpe.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,122 @@
+#// Usage: jjs getclassnpe.js -- <directory>
+
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * java.lang.Object.getClass() is sometimes used to do null check. This
+ * obfuscating Object.getClass() check relies on non-related intrinsic
+ * performance, which is potentially not available everywhere.
+ * See also http://cr.openjdk.java.net/~shade/scratch/NullChecks.java
+ * This nashorn script checks for such uses in your .java files in the
+ * given directory (recursively).
+ */
+
+if (arguments.length == 0) {
+    print("Usage: jjs getclassnpe.js -- <directory>");
+    exit(1);
+}
+
+// Java types used
+var File = Java.type("java.io.File");
+var Files = Java.type("java.nio.file.Files");
+var StringArray = Java.type("java.lang.String[]");
+var ToolProvider = Java.type("javax.tools.ToolProvider");
+var MethodInvocationTree = Java.type("com.sun.source.tree.MethodInvocationTree");
+var TreeScanner = Java.type("com.sun.source.util.TreeScanner");
+
+// parse a specific .java file to check if it uses
+// Object.getClass() for null check.
+function checkGetClassNPE() {
+    // get the system compiler tool
+    var compiler = ToolProvider.systemJavaCompiler;
+    // get standard file manager
+    var fileMgr = compiler.getStandardFileManager(null, null, null);
+    // Using Java.to convert script array (arguments) to a Java String[]
+    var compUnits = fileMgr.getJavaFileObjects(
+        Java.to(arguments, StringArray));
+    // create a new compilation task
+    var task = compiler.getTask(null, fileMgr, null, null, null, compUnits);
+    // subclass SimpleTreeVisitor - to check for obj.getClass(); statements
+    var GetClassNPEChecker = Java.extend(TreeScanner);
+
+    var visitor = new GetClassNPEChecker() {
+        lineMap: null,
+        sourceFile: null,
+
+        // save compilation unit details for reporting
+        visitCompilationUnit: function(node, p) {
+           this.sourceFile = node.sourceFile;
+           this.lineMap = node.lineMap;
+           return Java.super(visitor).visitCompilationUnit(node, p);
+        },
+
+        // look for "foo.getClass();" expression statements
+        visitExpressionStatement: function(node, p) {
+            var expr = node.expression;
+            if (expr instanceof MethodInvocationTree) {
+                var name = String(expr.methodSelect.identifier);
+
+                // will match any "getClass" call with zero arguments!
+                if (name == "getClass" && expr.arguments.size() == 0) {
+                    print(this.sourceFile.getName()
+                     + " @ "
+                     + this.lineMap.getLineNumber(node.pos)
+                     + ":"
+                     + this.lineMap.getColumnNumber(node.pos));
+
+                    print("\t", node);
+                }
+            }
+        }
+    }
+
+    for each (var cu in task.parse()) {
+        cu.accept(visitor, null);
+    }
+}
+
+// for each ".java" file in the directory (recursively)
+function main(dir) {
+    Files.walk(dir.toPath()).
+      forEach(function(p) {
+          var name = p.toFile().absolutePath;
+          if (name.endsWith(".java")) {
+              try {
+                  checkGetClassNPE(p.toFile().getAbsolutePath());
+              } catch (e) {
+                  print(e);
+              }
+          }
+      });
+}
+
+main(new File(arguments[0]));
--- a/samples/javashell.js	Wed Feb 11 12:18:58 2015 -0800
+++ b/samples/javashell.js	Wed Feb 11 18:55:44 2015 -0800
@@ -40,6 +40,7 @@
 var Arrays = Java.type("java.util.Arrays");
 var BufferedReader = Java.type("java.io.BufferedReader");
 var FileWriter = Java.type("java.io.FileWriter");
+var List = Java.type("java.util.List");
 var LocalDateTime = Java.type("java.time.LocalDateTime");
 var InputStreamReader = Java.type("java.io.InputStreamReader");
 var PrintWriter = Java.type("java.io.PrintWriter");
@@ -121,7 +122,7 @@
 // execute code command
 function exec(args) {
     // build child process and start it!
-    new ProcessBuilder(Arrays.asList(args.split(' ')))
+    new ProcessBuilder(Java.to(args.split(' '), List))
          .inheritIO()
          .start()
          .waitFor();
--- a/samples/shell.js	Wed Feb 11 12:18:58 2015 -0800
+++ b/samples/shell.js	Wed Feb 11 18:55:44 2015 -0800
@@ -42,6 +42,7 @@
     var Arrays = Java.type("java.util.Arrays");
     var BufferedReader = Java.type("java.io.BufferedReader");
     var InputStreamReader = Java.type("java.io.InputStreamReader");
+    var List = Java.type("java.util.List");
     var ProcessBuilder = Java.type("java.lang.ProcessBuilder");
     var System = Java.type("java.lang.System");
 
@@ -66,7 +67,7 @@
                     }
                 } else {
                     // build child process and start it!
-                    new ProcessBuilder(Arrays.asList(args))
+                    new ProcessBuilder(Java.to(args, List))
                         .inheritIO()
                         .start()
                         .waitFor();
--- a/src/jdk/internal/dynalink/DynamicLinker.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/internal/dynalink/DynamicLinker.java	Wed Feb 11 18:55:44 2015 -0800
@@ -88,6 +88,7 @@
 import java.lang.invoke.MethodType;
 import java.lang.invoke.MutableCallSite;
 import java.util.List;
+import java.util.Objects;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.GuardingDynamicLinker;
 import jdk.internal.dynalink.linker.LinkRequest;
@@ -252,7 +253,7 @@
         // Make sure we filter the invocation before linking it into the call site. This is typically used to match the
         // return type of the invocation to the call site.
         guardedInvocation = prelinkFilter.filter(guardedInvocation, linkRequest, linkerServices);
-        guardedInvocation.getClass(); // null pointer check
+        Objects.requireNonNull(guardedInvocation);
 
         int newRelinkCount = relinkCount;
         // Note that the short-circuited "&&" evaluation below ensures we'll increment the relinkCount until
--- a/src/jdk/internal/dynalink/beans/StaticClass.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/internal/dynalink/beans/StaticClass.java	Wed Feb 11 18:55:44 2015 -0800
@@ -84,6 +84,7 @@
 package jdk.internal.dynalink.beans;
 
 import java.io.Serializable;
+import java.util.Objects;
 
 /**
  * Object that represents the static facet of a class (its static methods, properties, and fields, as well as
@@ -106,8 +107,7 @@
     private final Class<?> clazz;
 
     /*private*/ StaticClass(final Class<?> clazz) {
-        clazz.getClass(); // NPE check
-        this.clazz = clazz;
+        this.clazz = Objects.requireNonNull(clazz);
     }
 
     /**
--- a/src/jdk/internal/dynalink/linker/GuardedInvocation.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/internal/dynalink/linker/GuardedInvocation.java	Wed Feb 11 18:55:44 2015 -0800
@@ -91,6 +91,7 @@
 import java.lang.invoke.SwitchPoint;
 import java.lang.invoke.WrongMethodTypeException;
 import java.util.List;
+import java.util.Objects;
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.support.Guards;
 
@@ -170,8 +171,7 @@
      * @throws NullPointerException if invocation is null.
      */
     public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard, final SwitchPoint switchPoint, final Class<? extends Throwable> exception) {
-        invocation.getClass(); // NPE check
-        this.invocation = invocation;
+        this.invocation = Objects.requireNonNull(invocation);
         this.guard = guard;
         this.switchPoints = switchPoint == null ? null : new SwitchPoint[] { switchPoint };
         this.exception = exception;
@@ -190,8 +190,7 @@
      * @throws NullPointerException if invocation is null.
      */
     public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard, final SwitchPoint[] switchPoints, final Class<? extends Throwable> exception) {
-        invocation.getClass(); // NPE check
-        this.invocation = invocation;
+        this.invocation = Objects.requireNonNull(invocation);
         this.guard = guard;
         this.switchPoints = switchPoints == null ? null : switchPoints.clone();
         this.exception = exception;
--- a/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java	Wed Feb 11 18:55:44 2015 -0800
@@ -91,6 +91,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.StringTokenizer;
 import java.util.WeakHashMap;
 import jdk.internal.dynalink.CallSiteDescriptor;
@@ -123,9 +124,9 @@
      * in fact return a weakly-referenced canonical instance.
      */
     public static CallSiteDescriptor create(final Lookup lookup, final String name, final MethodType methodType) {
-        name.getClass(); // NPE check
-        methodType.getClass(); // NPE check
-        lookup.getClass(); // NPE check
+        Objects.requireNonNull(name);
+        Objects.requireNonNull(methodType);
+        Objects.requireNonNull(lookup);
         final String[] tokenizedName = tokenizeName(name);
         if(isPublicLookup(lookup)) {
             return getCanonicalPublicDescriptor(createPublicCallSiteDescriptor(tokenizedName, methodType));
--- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Wed Feb 11 18:55:44 2015 -0800
@@ -39,6 +39,7 @@
 import java.security.ProtectionDomain;
 import java.text.MessageFormat;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.ResourceBundle;
 import javax.script.AbstractScriptEngine;
 import javax.script.Bindings;
@@ -360,7 +361,7 @@
     }
 
     private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException {
-        name.getClass(); // null check
+        Objects.requireNonNull(name);
         assert !(selfObject instanceof ScriptObject) : "raw ScriptObject not expected here";
 
         Global invokeGlobal = null;
--- a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java	Wed Feb 11 18:55:44 2015 -0800
@@ -28,6 +28,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import javax.script.ScriptEngine;
 import javax.script.ScriptEngineFactory;
 import jdk.nashorn.internal.runtime.Context;
@@ -177,7 +178,7 @@
      *         denies {@code RuntimePermission("nashorn.setConfig")}
      */
     public ScriptEngine getScriptEngine(final ClassFilter classFilter) {
-        classFilter.getClass(); // null check
+        Objects.requireNonNull(classFilter);
         return newEngine(DEFAULT_OPTIONS, getAppClassLoader(), classFilter);
     }
 
@@ -192,7 +193,7 @@
      *         denies {@code RuntimePermission("nashorn.setConfig")}
      */
     public ScriptEngine getScriptEngine(final String... args) {
-        args.getClass(); // null check
+        Objects.requireNonNull(args);
         return newEngine(args, getAppClassLoader(), null);
     }
 
@@ -208,7 +209,7 @@
      *         denies {@code RuntimePermission("nashorn.setConfig")}
      */
     public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) {
-        args.getClass(); // null check
+        Objects.requireNonNull(args);
         return newEngine(args, appLoader, null);
     }
 
@@ -225,8 +226,8 @@
      *         denies {@code RuntimePermission("nashorn.setConfig")}
      */
     public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
-        args.getClass(); // null check
-        classFilter.getClass(); // null check
+        Objects.requireNonNull(args);
+        Objects.requireNonNull(classFilter);
         return newEngine(args, appLoader, classFilter);
     }
 
--- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java	Wed Feb 11 18:55:44 2015 -0800
@@ -39,6 +39,7 @@
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Callable;
 import javax.script.Bindings;
@@ -180,7 +181,7 @@
      * @return return value of function
      */
     public Object callMember(final String functionName, final Object... args) {
-        functionName.getClass(); // null check
+        Objects.requireNonNull(functionName);
         final Global oldGlobal = Context.getGlobal();
         final boolean globalChanged = (oldGlobal != global);
 
@@ -213,7 +214,7 @@
 
     @Override
     public Object getMember(final String name) {
-        name.getClass();
+        Objects.requireNonNull(name);
         return inGlobal(new Callable<Object>() {
             @Override public Object call() {
                 return wrap(sobj.get(name), global);
@@ -232,7 +233,7 @@
 
     @Override
     public boolean hasMember(final String name) {
-        name.getClass();
+        Objects.requireNonNull(name);
         return inGlobal(new Callable<Boolean>() {
             @Override public Boolean call() {
                 return sobj.has(name);
@@ -251,13 +252,13 @@
 
     @Override
     public void removeMember(final String name) {
-        name.getClass();
+        Objects.requireNonNull(name);
         remove(name);
     }
 
     @Override
     public void setMember(final String name, final Object value) {
-        name.getClass();
+        Objects.requireNonNull(name);
         put(name, value);
     }
 
@@ -425,9 +426,7 @@
 
     @Override
     public void putAll(final Map<? extends String, ? extends Object> map) {
-        if (map == null) {
-            throw new NullPointerException("map is null");
-        }
+        Objects.requireNonNull(map, "map is null");
         final ScriptObject oldGlobal = Context.getGlobal();
         final boolean globalChanged = (oldGlobal != global);
         inGlobal(new Callable<Object>() {
@@ -449,7 +448,7 @@
         checkKey(key);
         return inGlobal(new Callable<Object>() {
             @Override public Object call() {
-                return wrap(sobj.remove(key, strict), global);
+                return translateUndefined(wrap(sobj.remove(key, strict), global));
             }
         });
     }
@@ -804,9 +803,9 @@
      * @throws IllegalArgumentException if key is empty string
      */
     private static void checkKey(final Object key) {
-        if (key == null) {
-            throw new NullPointerException("key can not be null");
-        } else if (!(key instanceof String)) {
+        Objects.requireNonNull(key, "key can not be null");
+
+        if (!(key instanceof String)) {
             throw new ClassCastException("key should be a String. It is " + key.getClass().getName() + " instead.");
         } else if (((String)key).length() == 0) {
             throw new IllegalArgumentException("key can not be empty");
--- a/src/jdk/nashorn/api/scripting/URLReader.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/api/scripting/URLReader.java	Wed Feb 11 18:55:44 2015 -0800
@@ -30,6 +30,7 @@
 import java.io.Reader;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.Objects;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
@@ -77,8 +78,7 @@
      * @throws NullPointerException if url is null
      */
     public URLReader(final URL url, final Charset cs) {
-        // null check
-        url.getClass();
+        Objects.requireNonNull(url);
         this.url = url;
         this.cs  = cs;
     }
--- a/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Feb 11 18:55:44 2015 -0800
@@ -919,9 +919,7 @@
     @Override
     public Node leaveTryNode(final TryNode tryNode) {
         tryNode.setException(exceptionSymbol());
-        if (tryNode.getFinallyBody() != null) {
-            tryNode.setFinallyCatchAll(exceptionSymbol());
-        }
+        assert tryNode.getFinallyBody() == null;
 
         end(tryNode);
 
--- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Feb 11 18:55:44 2015 -0800
@@ -85,7 +85,6 @@
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.BlockStatement;
 import jdk.nashorn.internal.ir.BreakNode;
-import jdk.nashorn.internal.ir.BreakableNode;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
@@ -102,6 +101,7 @@
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LexicalContextNode;
@@ -1110,7 +1110,14 @@
 
     @Override
     public boolean enterBlock(final Block block) {
-        method.label(block.getEntryLabel());
+        final Label entryLabel = block.getEntryLabel();
+        if (entryLabel.isBreakTarget()) {
+            // Entry label is a break target only for an inlined finally block.
+            assert !method.isReachable();
+            method.breakLabel(entryLabel, lc.getUsedSlotCount());
+        } else {
+            method.label(entryLabel);
+        }
         if(!method.isReachable()) {
             return false;
         }
@@ -1240,6 +1247,11 @@
         return enterJumpStatement(breakNode);
     }
 
+    @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterJumpStatement(jumpToInlinedFinally);
+    }
+
     private boolean enterJumpStatement(final JumpStatement jump) {
         if(!method.isReachable()) {
             return false;
@@ -1247,9 +1259,8 @@
         enterStatement(jump);
 
         method.beforeJoinPoint(jump);
-        final BreakableNode target = jump.getTarget(lc);
-        popScopesUntil(target);
-        final Label targetLabel = jump.getTargetLabel(target);
+        popScopesUntil(jump.getPopScopeLimit(lc));
+        final Label targetLabel = jump.getTargetLabel(lc);
         targetLabel.markAsBreakTarget();
         method._goto(targetLabel);
 
@@ -3053,6 +3064,14 @@
         if (method.isReachable()) {
             method._goto(skip);
         }
+
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            TryNode.getLabelledInlinedFinallyBlock(inlinedFinally).accept(this);
+            // All inlined finallies end with a jump or a return
+            assert !method.isReachable();
+        }
+
+
         method._catch(recovery);
         method.store(vmException, EXCEPTION_TYPE);
 
@@ -3103,6 +3122,7 @@
             if (isConditionalCatch) {
                 loadExpressionAsBoolean(exceptionCondition);
                 nextCatch = new Label("next_catch");
+                nextCatch.markAsBreakTarget();
                 method.ifeq(nextCatch);
             } else {
                 nextCatch = null;
@@ -3111,15 +3131,14 @@
             catchBody.accept(this);
             leaveBlock(catchBlock);
             lc.pop(catchBlock);
-            if(method.isReachable()) {
-                method._goto(afterCatch);
-            }
             if(nextCatch != null) {
-                method.label(nextCatch);
-            }
-        }
-
-        assert !method.isReachable();
+                if(method.isReachable()) {
+                    method._goto(afterCatch);
+                }
+                method.breakLabel(nextCatch, lc.getUsedSlotCount());
+            }
+        }
+
         // afterCatch could be the same as skip, except that we need to establish that the vmException is dead.
         method.label(afterCatch);
         if(method.isReachable()) {
@@ -3128,6 +3147,8 @@
         method.label(skip);
 
         // Finally body is always inlined elsewhere so it doesn't need to be emitted
+        assert tryNode.getFinallyBody() == null;
+
         return false;
     }
 
--- a/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java	Wed Feb 11 18:55:44 2015 -0800
@@ -74,7 +74,7 @@
     /** size of next free slot vector */
     private int nextFreeSlotsSize;
 
-    private boolean isWithBoundary(final LexicalContextNode node) {
+    private boolean isWithBoundary(final Object node) {
         return node instanceof Block && !isEmpty() && peek() instanceof WithNode;
     }
 
@@ -102,7 +102,7 @@
     }
 
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         final T popped = super.pop(node);
         if (isWithBoundary(node)) {
             dynamicScopeCount--;
--- a/src/jdk/nashorn/internal/codegen/CompileUnit.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/CompileUnit.java	Wed Feb 11 18:55:44 2015 -0800
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.codegen;
 
 import java.io.Serializable;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import jdk.nashorn.internal.ir.CompileUnitHolder;
@@ -113,7 +114,7 @@
      * @param clazz class with code for this compile unit
      */
     void setCode(final Class<?> clazz) {
-        clazz.getClass(); // null check
+        Objects.requireNonNull(clazz);
         this.clazz = clazz;
         // Revisit this - refactor to avoid null-ed out non-final fields
         // null out emitter
--- a/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Feb 11 18:55:44 2015 -0800
@@ -62,6 +62,7 @@
 import jdk.nashorn.internal.ir.JoinPredecessor;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LexicalContextNode;
@@ -529,8 +530,7 @@
             return false;
         }
         assertTypeStackIsEmpty();
-        final BreakableNode target = jump.getTarget(lc);
-        jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target));
+        jumpToLabel(jump, jump.getTargetLabel(lc), getBreakTargetTypes(jump.getPopScopeLimit(lc)));
         doesNotContinueSequentially();
         return false;
     }
@@ -784,6 +784,11 @@
     }
 
     @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterJumpStatement(jumpToInlinedFinally);
+    }
+
+    @Override
     public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
         if (literalNode instanceof ArrayLiteralNode) {
             final List<Expression> expressions = ((ArrayLiteralNode)literalNode).getElementExpressions();
@@ -1042,6 +1047,17 @@
         }
         doesNotContinueSequentially();
 
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            final Block finallyBody = TryNode.getLabelledInlinedFinallyBlock(inlinedFinally);
+            joinOnLabel(finallyBody.getEntryLabel());
+            // NOTE: the jump to inlined finally can end up in dead code, so it is not necessarily reachable.
+            if (reachable) {
+                finallyBody.accept(this);
+                // All inlined finallies end with a jump or a return
+                assert !reachable;
+            }
+        }
+
         joinOnLabel(catchLabel);
         for(final CatchNode catchNode: tryNode.getCatches()) {
             final IdentNode exception = catchNode.getException();
@@ -1125,7 +1141,7 @@
         return false;
     };
 
-    private Map<Symbol, LvarType> getBreakTargetTypes(final BreakableNode target) {
+    private Map<Symbol, LvarType> getBreakTargetTypes(final LexicalContextNode target) {
         // Remove symbols defined in the the blocks that are being broken out of.
         Map<Symbol, LvarType> types = localVariableTypes;
         for(final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) {
@@ -1380,7 +1396,11 @@
                 if(node instanceof JoinPredecessor) {
                     final JoinPredecessor original = joinPredecessors.pop();
                     assert original.getClass() == node.getClass() : original.getClass().getName() + "!=" + node.getClass().getName();
-                    return (Node)setLocalVariableConversion(original, (JoinPredecessor)node);
+                    final JoinPredecessor newNode = setLocalVariableConversion(original, (JoinPredecessor)node);
+                    if (newNode instanceof LexicalContextNode) {
+                        lc.replace((LexicalContextNode)node, (LexicalContextNode)newNode);
+                    }
+                    return (Node)newNode;
                 }
                 return node;
             }
--- a/src/jdk/nashorn/internal/codegen/Lower.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/Lower.java	Wed Feb 11 18:55:44 2015 -0800
@@ -56,9 +56,11 @@
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
 import jdk.nashorn.internal.ir.LoopNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ReturnNode;
@@ -115,7 +117,7 @@
                 for (final Statement statement : statements) {
                     if (!terminated) {
                         newStatements.add(statement);
-                        if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
+                        if (statement.isTerminal() || statement instanceof JumpStatement) { //TODO hasGoto? But some Loops are hasGoto too - why?
                             terminated = true;
                         }
                     } else {
@@ -183,6 +185,12 @@
     }
 
     @Override
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        addStatement(jumpToInlinedFinally);
+        return false;
+    }
+
+    @Override
     public boolean enterEmptyNode(final EmptyNode emptyNode) {
         return false;
     }
@@ -318,8 +326,8 @@
         return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
     }
 
-    private static Node ensureUniqueNamesIn(final Node node) {
-        return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+    private static <T extends Node> T ensureUniqueNamesIn(final T node) {
+        return (T)node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
             @Override
             public Node leaveFunctionNode(final FunctionNode functionNode) {
                 final String name = functionNode.getName();
@@ -333,15 +341,15 @@
         });
     }
 
-    private static List<Statement> copyFinally(final Block finallyBody) {
+    private static Block createFinallyBlock(final Block finallyBody) {
         final List<Statement> newStatements = new ArrayList<>();
         for (final Statement statement : finallyBody.getStatements()) {
-            newStatements.add((Statement)ensureUniqueNamesIn(statement));
+            newStatements.add(statement);
             if (statement.hasTerminalFlags()) {
-                return newStatements;
+                break;
             }
         }
-        return newStatements;
+        return finallyBody.setStatements(null, newStatements);
     }
 
     private Block catchAllBlock(final TryNode tryNode) {
@@ -367,28 +375,24 @@
         return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
     }
 
-    private static boolean isTerminal(final List<Statement> statements) {
-        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
+    private static boolean isTerminalFinally(final Block finallyBlock) {
+        return finallyBlock.getLastStatement().hasTerminalFlags();
     }
 
     /**
      * Splice finally code into all endpoints of a trynode
      * @param tryNode the try node
-     * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
+     * @param rethrow the rethrowing throw nodes from the synthetic catch block
      * @param finallyBody the code in the original finally block
      * @return new try node after splicing finally code (same if nop)
      */
-    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
+    private TryNode spliceFinally(final TryNode tryNode, final ThrowNode rethrow, final Block finallyBody) {
         assert tryNode.getFinallyBody() == null;
 
+        final Block finallyBlock = createFinallyBlock(finallyBody);
+        final ArrayList<Block> inlinedFinallies = new ArrayList<>();
+        final FunctionNode fn = lc.getCurrentFunction();
         final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
-            final List<Node> insideTry = new ArrayList<>();
-
-            @Override
-            public boolean enterDefault(final Node node) {
-                insideTry.add(node);
-                return true;
-            }
 
             @Override
             public boolean enterFunctionNode(final FunctionNode functionNode) {
@@ -398,12 +402,8 @@
 
             @Override
             public Node leaveThrowNode(final ThrowNode throwNode) {
-                if (rethrows.contains(throwNode)) {
-                    final List<Statement> newStatements = copyFinally(finallyBody);
-                    if (!isTerminal(newStatements)) {
-                        newStatements.add(throwNode);
-                    }
-                    return BlockStatement.createReplacement(throwNode, newStatements);
+                if (rethrow == throwNode) {
+                    return new BlockStatement(prependFinally(finallyBlock, throwNode));
                 }
                 return throwNode;
             }
@@ -419,58 +419,94 @@
             }
 
             private Node leaveJumpStatement(final JumpStatement jump) {
-                return copy(jump, (Node)jump.getTarget(Lower.this.lc));
+                // NOTE: leaveJumpToInlinedFinally deliberately does not delegate to this method, only break and
+                // continue are edited. JTIF nodes should not be changed, rather the surroundings of
+                // break/continue/return that were moved into the inlined finally block itself will be changed.
+
+                // If this visitor's lc doesn't find the target of the jump, it means it's external to the try block.
+                if (jump.getTarget(lc) == null) {
+                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, jump));
+                }
+                return jump;
             }
 
             @Override
             public Node leaveReturnNode(final ReturnNode returnNode) {
-                final Expression expr  = returnNode.getExpression();
-                final List<Statement> newStatements = new ArrayList<>();
-
-                final Expression resultNode;
-                if (expr != null) {
-                    //we need to evaluate the result of the return in case it is complex while
-                    //still in the try block, store it in a result value and return it afterwards
-                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
-                    newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
+                final Expression expr = returnNode.getExpression();
+                if (isTerminalFinally(finallyBlock)) {
+                    if (expr == null) {
+                        // Terminal finally; no return expression.
+                        return createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock));
+                    }
+                    // Terminal finally; has a return expression.
+                    final List<Statement> newStatements = new ArrayList<>(2);
+                    final int retLineNumber = returnNode.getLineNumber();
+                    final long retToken = returnNode.getToken();
+                    // Expression is evaluated for side effects.
+                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, returnNode.getFinish(), expr));
+                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, ensureUniqueNamesIn(finallyBlock)));
+                    return new BlockStatement(retLineNumber, new Block(retToken, finallyBlock.getFinish(), newStatements));
+                } else if (expr == null || expr instanceof PrimitiveLiteralNode<?> || (expr instanceof IdentNode && RETURN.symbolName().equals(((IdentNode)expr).getName()))) {
+                    // Nonterminal finally; no return expression, or returns a primitive literal, or returns :return.
+                    // Just move the return expression into the finally block.
+                    return createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode));
                 } else {
-                    resultNode = null;
+                    // We need to evaluate the result of the return in case it is complex while still in the try block,
+                    // store it in :return, and return it afterwards.
+                    final List<Statement> newStatements = new ArrayList<>();
+                    final int retLineNumber = returnNode.getLineNumber();
+                    final long retToken = returnNode.getToken();
+                    final int retFinish = returnNode.getFinish();
+                    final Expression resultNode = new IdentNode(expr.getToken(), expr.getFinish(), RETURN.symbolName());
+                    // ":return = <expr>;"
+                    newStatements.add(new ExpressionStatement(retLineNumber, retToken, retFinish, new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
+                    // inline finally and end it with "return :return;"
+                    newStatements.add(createJumpToInlinedFinally(fn, inlinedFinallies, prependFinally(finallyBlock, returnNode.setExpression(resultNode))));
+                    return new BlockStatement(retLineNumber, new Block(retToken, retFinish, newStatements));
                 }
-
-                newStatements.addAll(copyFinally(finallyBody));
-                if (!isTerminal(newStatements)) {
-                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
-                }
-
-                return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
-            }
-
-            private Node copy(final Statement endpoint, final Node targetNode) {
-                if (!insideTry.contains(targetNode)) {
-                    final List<Statement> newStatements = copyFinally(finallyBody);
-                    if (!isTerminal(newStatements)) {
-                        newStatements.add(endpoint);
-                    }
-                    return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
-                }
-                return endpoint;
             }
         });
-
-        addStatement(newTryNode);
-        for (final Node statement : finallyBody.getStatements()) {
-            addStatement((Statement)statement);
-        }
+        addStatement(inlinedFinallies.isEmpty() ? newTryNode : newTryNode.setInlinedFinallies(lc, inlinedFinallies));
+        // TODO: if finallyStatement is terminal, we could just have sites of inlined finallies jump here.
+        addStatement(new BlockStatement(finallyBlock));
 
         return newTryNode;
     }
 
+    private static JumpToInlinedFinally createJumpToInlinedFinally(final FunctionNode fn, final List<Block> inlinedFinallies, final Block finallyBlock) {
+        final String labelName = fn.uniqueName(":finally");
+        final long token = finallyBlock.getToken();
+        final int finish = finallyBlock.getFinish();
+        inlinedFinallies.add(new Block(token, finish, new LabelNode(finallyBlock.getFirstStatementLineNumber(),
+                token, finish, labelName, finallyBlock)));
+        return new JumpToInlinedFinally(labelName);
+    }
+
+    private static Block prependFinally(final Block finallyBlock, final Statement statement) {
+        final Block inlinedFinally = ensureUniqueNamesIn(finallyBlock);
+        if (isTerminalFinally(finallyBlock)) {
+            return inlinedFinally;
+        }
+        final List<Statement> stmts = inlinedFinally.getStatements();
+        final List<Statement> newStmts = new ArrayList<>(stmts.size() + 1);
+        newStmts.addAll(stmts);
+        newStmts.add(statement);
+        return new Block(inlinedFinally.getToken(), statement.getFinish(), newStmts);
+    }
+
     @Override
     public Node leaveTryNode(final TryNode tryNode) {
         final Block finallyBody = tryNode.getFinallyBody();
+        TryNode newTryNode = tryNode.setFinallyBody(lc, null);
 
-        if (finallyBody == null) {
-            return addStatement(ensureUnconditionalCatch(tryNode));
+        // No finally or empty finally
+        if (finallyBody == null || finallyBody.getStatementCount() == 0) {
+            final List<CatchNode> catches = newTryNode.getCatches();
+            if (catches == null || catches.isEmpty()) {
+                // A completely degenerate try block: empty finally, no catches. Replace it with try body.
+                return addStatement(new BlockStatement(tryNode.getBody()));
+            }
+            return addStatement(ensureUnconditionalCatch(newTryNode));
         }
 
         /*
@@ -496,11 +532,9 @@
          *   now splice in finally code wherever needed
          *
          */
-        TryNode newTryNode;
-
         final Block catchAll = catchAllBlock(tryNode);
 
-        final List<ThrowNode> rethrows = new ArrayList<>();
+        final List<ThrowNode> rethrows = new ArrayList<>(1);
         catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
             @Override
             public boolean enterThrowNode(final ThrowNode throwNode) {
@@ -510,20 +544,18 @@
         });
         assert rethrows.size() == 1;
 
-        if (tryNode.getCatchBlocks().isEmpty()) {
-            newTryNode = tryNode.setFinallyBody(null);
-        } else {
-            final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
-            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
+        if (!tryNode.getCatchBlocks().isEmpty()) {
+            final Block outerBody = new Block(newTryNode.getToken(), newTryNode.getFinish(), ensureUnconditionalCatch(newTryNode));
+            newTryNode = newTryNode.setBody(lc, outerBody).setCatchBlocks(lc, null);
         }
 
-        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
+        newTryNode = newTryNode.setCatchBlocks(lc, Arrays.asList(catchAll));
 
         /*
          * Now that the transform is done, we have to go into the try and splice
          * the finally block in front of any statement that is outside the try
          */
-        return spliceFinally(newTryNode, rethrows, finallyBody);
+        return (TryNode)lc.replace(tryNode, spliceFinally(newTryNode, rethrows.get(0), finallyBody));
     }
 
     private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
@@ -535,7 +567,7 @@
         final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
 
         newCatchBlocks.add(catchAllBlock(tryNode));
-        return tryNode.setCatchBlocks(newCatchBlocks);
+        return tryNode.setCatchBlocks(lc, newCatchBlocks);
     }
 
     @Override
--- a/src/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/SplitIntoFunctions.java	Wed Feb 11 18:55:44 2015 -0800
@@ -51,6 +51,7 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.JumpStatement;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ReturnNode;
@@ -355,6 +356,11 @@
         return leaveJumpNode(continueNode);
     }
 
+    @Override
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return leaveJumpNode(jumpToInlinedFinally);
+    }
+
     private JumpStatement leaveJumpNode(final JumpStatement jump) {
         if (inSplitNode()) {
             final SplitState splitState = getCurrentSplitState();
--- a/src/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/codegen/WeighNodes.java	Wed Feb 11 18:55:44 2015 -0800
@@ -40,6 +40,7 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
@@ -197,6 +198,12 @@
         return indexNode;
     }
 
+    @Override
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        weight += BREAK_WEIGHT;
+        return jumpToInlinedFinally;
+    }
+
     @SuppressWarnings("rawtypes")
     @Override
     public boolean enterLiteralNode(final LiteralNode literalNode) {
--- a/src/jdk/nashorn/internal/ir/Block.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/Block.java	Wed Feb 11 18:55:44 2015 -0800
@@ -298,6 +298,14 @@
     }
 
     /**
+     * Returns the last statement in the block.
+     * @return the last statement in the block, or null if the block has no statements.
+     */
+    public Statement getLastStatement() {
+        return statements.isEmpty() ? null : statements.get(statements.size() - 1);
+    }
+
+    /**
      * Reset the statement list for this block
      *
      * @param lc lexical context
--- a/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Wed Feb 11 18:55:44 2015 -0800
@@ -74,7 +74,7 @@
 
     @SuppressWarnings("unchecked")
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         T expected = node;
         if (node instanceof Block) {
             final List<Statement> newStatements = popStatements();
--- a/src/jdk/nashorn/internal/ir/BlockStatement.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/BlockStatement.java	Wed Feb 11 18:55:44 2015 -0800
@@ -40,6 +40,15 @@
     /**
      * Constructor
      *
+     * @param block the block to execute
+     */
+    public BlockStatement(final Block block) {
+        this(block.getFirstStatementLineNumber(), block);
+    }
+
+    /**
+     * Constructor
+     *
      * @param lineNumber line number
      * @param block the block to execute
      */
--- a/src/jdk/nashorn/internal/ir/BreakNode.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/BreakNode.java	Wed Feb 11 18:55:44 2015 -0800
@@ -77,7 +77,7 @@
     }
 
     @Override
-    public Label getTargetLabel(final BreakableNode target) {
+    Label getTargetLabel(final BreakableNode target) {
         return target.getBreakLabel();
     }
 }
--- a/src/jdk/nashorn/internal/ir/ContinueNode.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/ContinueNode.java	Wed Feb 11 18:55:44 2015 -0800
@@ -78,7 +78,7 @@
     }
 
     @Override
-    public Label getTargetLabel(final BreakableNode target) {
+    Label getTargetLabel(final BreakableNode target) {
         return ((LoopNode)target).getContinueLabel();
     }
 }
--- a/src/jdk/nashorn/internal/ir/JumpStatement.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/JumpStatement.java	Wed Feb 11 18:55:44 2015 -0800
@@ -101,7 +101,26 @@
      * @throws ClassCastException if invoked on the kind of breakable node that this jump statement is not prepared to
      * handle.
      */
-    public abstract Label getTargetLabel(final BreakableNode target);
+    abstract Label getTargetLabel(final BreakableNode target);
+
+    /**
+     * Returns the label this jump statement targets.
+     * @param lc the lexical context
+     * @return the label this jump statement targets.
+     */
+    public Label getTargetLabel(final LexicalContext lc) {
+        return getTargetLabel(getTarget(lc));
+    }
+
+    /**
+     * Returns the limit node for popping scopes when this jump statement is effected.
+     * @param lc the current lexical context
+     * @return the limit node for popping scopes when this jump statement is effected.
+     */
+    public LexicalContextNode getPopScopeLimit(final LexicalContext lc) {
+        // In most cases (break and continue) this is equal to the target.
+        return getTarget(lc);
+    }
 
     @Override
     public JumpStatement setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/ir/JumpToInlinedFinally.java	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.ir;
+
+import java.util.Objects;
+import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+
+/**
+ * IR representation for synthetic jump into an inlined finally statement.
+ */
+@Immutable
+public final class JumpToInlinedFinally extends JumpStatement {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructor
+     *
+     * @param labelName  label name for inlined finally block
+     */
+    public JumpToInlinedFinally(final String labelName) {
+        super(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, Objects.requireNonNull(labelName));
+    }
+
+    private JumpToInlinedFinally(final JumpToInlinedFinally breakNode, final LocalVariableConversion conversion) {
+        super(breakNode, conversion);
+    }
+
+    @Override
+    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
+        if (visitor.enterJumpToInlinedFinally(this)) {
+            return visitor.leaveJumpToInlinedFinally(this);
+        }
+
+        return this;
+    }
+
+    @Override
+    JumpStatement createNewJumpStatement(final LocalVariableConversion conversion) {
+        return new JumpToInlinedFinally(this, conversion);
+    }
+
+    @Override
+    String getStatementName() {
+        return ":jumpToInlinedFinally";
+    }
+
+    @Override
+    public Block getTarget(final LexicalContext lc) {
+        return lc.getInlinedFinally(getLabelName());
+    }
+
+    @Override
+    public TryNode getPopScopeLimit(final LexicalContext lc) {
+        // Returns the try node to which this jump's target belongs. This will make scope popping also pop the scope
+        // for the body of the try block, if it needs scope.
+        return lc.getTryNodeForInlinedFinally(getLabelName());
+    }
+
+    @Override
+    Label getTargetLabel(final BreakableNode target) {
+        assert target != null;
+        // We're jumping to the entry of the inlined finally block
+        return ((Block)target).getEntryLabel();
+    }
+}
--- a/src/jdk/nashorn/internal/ir/LexicalContext.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/LexicalContext.java	Wed Feb 11 18:55:44 2015 -0800
@@ -190,7 +190,7 @@
      * @return the node that was popped
      */
     @SuppressWarnings("unchecked")
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         --sp;
         final LexicalContextNode popped = stack[sp];
         stack[sp] = null;
@@ -469,7 +469,7 @@
      * scopes that need to be explicitly popped in order to perform a break or continue jump within the current bytecode
      * method. For this reason, the method returns 0 if it encounters a {@code SplitNode} between the current location
      * and the break/continue target.
-     * @param until node to stop counting at. Must be within the current  function
+     * @param until node to stop counting at. Must be within the current function
      * @return number of with scopes encountered in the context
      */
     public int getScopeNestingLevelTo(final LexicalContextNode until) {
@@ -565,6 +565,36 @@
     }
 
     /**
+     * Find the inlined finally block node corresponding to this label.
+     * @param labelName label name to search for. Must not be null.
+     * @return closest inlined finally block with the given label
+     */
+    public Block getInlinedFinally(final String labelName) {
+        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
+            final Block inlinedFinally = iter.next().getInlinedFinally(labelName);
+            if (inlinedFinally != null) {
+                return inlinedFinally;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Find the try node for an inlined finally block corresponding to this label.
+     * @param labelName label name to search for. Must not be null.
+     * @return the try node to which the labelled inlined finally block belongs.
+     */
+    public TryNode getTryNodeForInlinedFinally(final String labelName) {
+        for (final NodeIterator<TryNode> iter = new NodeIterator<>(TryNode.class); iter.hasNext(); ) {
+            final TryNode tryNode = iter.next();
+            if (tryNode.getInlinedFinally(labelName) != null) {
+                return tryNode;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Check the lexical context for a given label node by name
      * @param name name of the label
      * @return LabelNode if found, null otherwise
@@ -592,6 +622,12 @@
                 return true;
             } else if (next == target) {
                 return false;
+            } else if (next instanceof TryNode) {
+                for(final Block inlinedFinally: ((TryNode)next).getInlinedFinallies()) {
+                    if (TryNode.getLabelledInlinedFinallyBlock(inlinedFinally) == target) {
+                        return false;
+                    }
+                }
             }
         }
         throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't");
--- a/src/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/LexicalContextNode.java	Wed Feb 11 18:55:44 2015 -0800
@@ -54,8 +54,8 @@
         static Node accept(final LexicalContextNode node, final NodeVisitor<? extends LexicalContext> visitor) {
             final LexicalContext lc = visitor.getLexicalContext();
             lc.push(node);
-            final LexicalContextNode newNode = (LexicalContextNode)node.accept(lc, visitor);
-            return (Node)lc.pop(newNode);
+            final Node newNode = node.accept(lc, visitor);
+            return lc.pop(newNode);
         }
     }
 }
--- a/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/OptimisticLexicalContext.java	Wed Feb 11 18:55:44 2015 -0800
@@ -115,7 +115,7 @@
     }
 
     @Override
-    public <T extends LexicalContextNode> T pop(final T node) {
+    public <T extends Node> T pop(final T node) {
         final T popped = super.pop(node);
         if (isEnabled) {
             if(node instanceof FunctionNode) {
--- a/src/jdk/nashorn/internal/ir/TryNode.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/TryNode.java	Wed Feb 11 18:55:44 2015 -0800
@@ -27,7 +27,9 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 
@@ -35,7 +37,7 @@
  * IR representation of a TRY statement.
  */
 @Immutable
-public final class TryNode extends Statement implements JoinPredecessor {
+public final class TryNode extends LexicalContextStatement implements JoinPredecessor {
     private static final long serialVersionUID = 1L;
 
     /** Try statements. */
@@ -47,12 +49,24 @@
     /** Finally clause. */
     private final Block finallyBody;
 
+    /**
+     * List of inlined finally blocks. The structure of every inlined finally is:
+     * Block(LabelNode(label, Block(finally-statements, (JumpStatement|ReturnNode)?))).
+     * That is, the block has a single LabelNode statement with the label and a block containing the
+     * statements of the inlined finally block with the jump or return statement appended (if the finally
+     * block was not terminal; the original jump/return is simply ignored if the finally block itself
+     * terminates). The reason for this somewhat strange arrangement is that we didn't want to create a
+     * separate class for the (label, BlockStatement pair) but rather reused the already available LabelNode.
+     * However, if we simply used List<LabelNode> without wrapping the label nodes in an additional Block,
+     * that would've thrown off visitors relying on BlockLexicalContext -- same reason why we never use
+     * Statement as the type of bodies of e.g. IfNode, WhileNode etc. but rather blockify them even when they're
+     * single statements.
+     */
+    private final List<Block> inlinedFinallies;
+
     /** Exception symbol. */
     private Symbol exception;
 
-    /** Catchall exception for finally expansion, where applicable */
-    private Symbol finallyCatchAll;
-
     private final LocalVariableConversion conversion;
 
     /**
@@ -71,21 +85,23 @@
         this.catchBlocks = catchBlocks;
         this.finallyBody = finallyBody;
         this.conversion  = null;
+        this.inlinedFinallies = Collections.emptyList();
     }
 
-    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion) {
+    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody, final LocalVariableConversion conversion, final List<Block> inlinedFinallies) {
         super(tryNode);
         this.body        = body;
         this.catchBlocks = catchBlocks;
         this.finallyBody = finallyBody;
         this.conversion  = conversion;
+        this.inlinedFinallies = inlinedFinallies;
         this.exception = tryNode.exception;
     }
 
     @Override
     public Node ensureUniqueLabels(final LexicalContext lc) {
         //try nodes are never in lex context
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
     }
 
     @Override
@@ -106,16 +122,16 @@
      * @param visitor IR navigating visitor.
      */
     @Override
-    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
+    public Node accept(final LexicalContext lc, NodeVisitor<? extends LexicalContext> visitor) {
         if (visitor.enterTryNode(this)) {
             // Need to do finallybody first for termination analysis. TODO still necessary?
             final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
             final Block newBody        = (Block)body.accept(visitor);
             return visitor.leaveTryNode(
-                setBody(newBody).
-                setFinallyBody(newFinallyBody).
-                setCatchBlocks(Node.accept(visitor, catchBlocks)).
-                setFinallyCatchAll(finallyCatchAll));
+                setBody(lc, newBody).
+                setFinallyBody(lc, newFinallyBody).
+                setCatchBlocks(lc, Node.accept(visitor, catchBlocks)).
+                setInlinedFinallies(lc, Node.accept(visitor, inlinedFinallies)));
         }
 
         return this;
@@ -136,14 +152,15 @@
 
     /**
      * Reset the body of this try block
+     * @param lc current lexical context
      * @param body new body
      * @return new TryNode or same if unchanged
      */
-    public TryNode setBody(final Block body) {
+    public TryNode setBody(final LexicalContext lc, final Block body) {
         if (this.body == body) {
             return this;
         }
-        return new TryNode(this,  body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this,  body, catchBlocks, finallyBody, conversion, inlinedFinallies));
     }
 
     /**
@@ -172,14 +189,15 @@
 
     /**
      * Set the catch blocks of this try
+     * @param lc current lexical context
      * @param catchBlocks list of catch blocks
      * @return new TryNode or same if unchanged
      */
-    public TryNode setCatchBlocks(final List<Block> catchBlocks) {
+    public TryNode setCatchBlocks(final LexicalContext lc, final List<Block> catchBlocks) {
         if (this.catchBlocks == catchBlocks) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
     }
 
     /**
@@ -200,27 +218,6 @@
     }
 
     /**
-     * Get the catch all symbol for this try block
-     * @return catch all symbol
-     */
-    public Symbol getFinallyCatchAll() {
-        return this.finallyCatchAll;
-    }
-
-    /**
-     * If a finally block exists, the synthetic catchall needs another symbol to
-     * store its throwable
-     * @param finallyCatchAll a symbol for the finally catch all exception
-     * @return new TryNode or same if unchanged
-     *
-     * TODO can this still be stateful?
-     */
-    public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) {
-        this.finallyCatchAll = finallyCatchAll;
-        return this;
-    }
-
-    /**
      * Get the body of the finally clause for this try
      * @return finally body, or null if no finally
      */
@@ -229,15 +226,87 @@
     }
 
     /**
+     * Get the inlined finally block with the given label name. This returns the actual finally block in the
+     * {@link LabelNode}, not the outer wrapper block for the {@link LabelNode}.
+     * @param labelName the name of the inlined finally's label
+     * @return the requested finally block, or null if no finally block's label matches the name.
+     */
+    public Block getInlinedFinally(final String labelName) {
+        for(final Block inlinedFinally: inlinedFinallies) {
+            final LabelNode labelNode = getInlinedFinallyLabelNode(inlinedFinally);
+            if (labelNode.getLabelName().equals(labelName)) {
+                return labelNode.getBody();
+            }
+        }
+        return null;
+    }
+
+    private static LabelNode getInlinedFinallyLabelNode(final Block inlinedFinally) {
+        return (LabelNode)inlinedFinally.getStatements().get(0);
+    }
+
+    /**
+     * Given an outer wrapper block for the {@link LabelNode} as returned by {@link #getInlinedFinallies()},
+     * returns its actual inlined finally block.
+     * @param inlinedFinally the outer block for inlined finally, as returned as an element of
+     * {@link #getInlinedFinallies()}.
+     * @return the block contained in the {@link LabelNode} contained in the passed block.
+     */
+    public static Block getLabelledInlinedFinallyBlock(final Block inlinedFinally) {
+        return getInlinedFinallyLabelNode(inlinedFinally).getBody();
+    }
+
+    /**
+     * Returns a list of inlined finally blocks. Note that this returns a list of {@link Block}s such that each one of
+     * them has a single {@link LabelNode}, which in turn contains the label name for the finally block and the
+     * actual finally block. To safely extract the actual finally block, use
+     * {@link #getLabelledInlinedFinallyBlock(Block)}.
+     * @return a list of inlined finally blocks.
+     */
+    public List<Block> getInlinedFinallies() {
+        return Collections.unmodifiableList(inlinedFinallies);
+    }
+
+    /**
      * Set the finally body of this try
+     * @param lc current lexical context
      * @param finallyBody new finally body
      * @return new TryNode or same if unchanged
      */
-    public TryNode setFinallyBody(final Block finallyBody) {
+    public TryNode setFinallyBody(final LexicalContext lc, final Block finallyBody) {
         if (this.finallyBody == finallyBody) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+    }
+
+    /**
+     * Set the inlined finally blocks of this try. Each element should be a block with a single statement that is a
+     * {@link LabelNode} with a unique label, and the block within the label node should contain the actual inlined
+     * finally block.
+     * @param lc current lexical context
+     * @param inlinedFinallies list of inlined finally blocks
+     * @return new TryNode or same if unchanged
+     */
+    public TryNode setInlinedFinallies(final LexicalContext lc, final List<Block> inlinedFinallies) {
+        if (this.inlinedFinallies == inlinedFinallies) {
+            return this;
+        }
+        assert checkInlinedFinallies(inlinedFinallies);
+        return Node.replaceInLexicalContext(lc, this, new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies));
+    }
+
+    private static boolean checkInlinedFinallies(final List<Block> inlinedFinallies) {
+        if (!inlinedFinallies.isEmpty()) {
+            final Set<String> labels = new HashSet<>();
+            for (final Block inlinedFinally : inlinedFinallies) {
+                final List<Statement> stmts = inlinedFinally.getStatements();
+                assert stmts.size() == 1;
+                final LabelNode ln = getInlinedFinallyLabelNode(inlinedFinally);
+                assert labels.add(ln.getLabelName()); // unique label
+            }
+        }
+        return true;
     }
 
     @Override
@@ -245,7 +314,7 @@
         if(this.conversion == conversion) {
             return this;
         }
-        return new TryNode(this, body, catchBlocks, finallyBody, conversion);
+        return new TryNode(this, body, catchBlocks, finallyBody, conversion, inlinedFinallies);
     }
 
     @Override
--- a/src/jdk/nashorn/internal/ir/debug/ObjectSizeCalculator.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/debug/ObjectSizeCalculator.java	Wed Feb 11 18:55:44 2015 -0800
@@ -38,6 +38,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * Contains utility methods for calculating the memory usage of objects. It
@@ -150,7 +151,7 @@
      * @param memoryLayoutSpecification a description of the JVM memory layout.
      */
     public ObjectSizeCalculator(final MemoryLayoutSpecification memoryLayoutSpecification) {
-        memoryLayoutSpecification.getClass();
+        Objects.requireNonNull(memoryLayoutSpecification);
         arrayHeaderSize = memoryLayoutSpecification.getArrayHeaderSize();
         objectHeaderSize = memoryLayoutSpecification.getObjectHeaderSize();
         objectPadding = memoryLayoutSpecification.getObjectPadding();
--- a/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java	Wed Feb 11 18:55:44 2015 -0800
@@ -391,6 +391,9 @@
             finallyBody.accept(this);
         }
 
+        for (final Block inlinedFinally : tryNode.getInlinedFinallies()) {
+            inlinedFinally.accept(this);
+        }
         return false;
     }
 
--- a/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java	Wed Feb 11 18:55:44 2015 -0800
@@ -43,6 +43,7 @@
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.JoinPredecessorExpression;
+import jdk.nashorn.internal.ir.JumpToInlinedFinally;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
@@ -473,6 +474,26 @@
     }
 
     /**
+     * Callback for entering a JumpToInlinedFinally
+     *
+     * @param  jumpToInlinedFinally the node
+     * @return true if traversal should continue and node children be traversed, false otherwise
+     */
+    public boolean enterJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return enterDefault(jumpToInlinedFinally);
+    }
+
+    /**
+     * Callback for leaving a JumpToInlinedFinally
+     *
+     * @param  jumpToInlinedFinally the node
+     * @return processed node, which will replace the original one, or the original node
+     */
+    public Node leaveJumpToInlinedFinally(final JumpToInlinedFinally jumpToInlinedFinally) {
+        return leaveDefault(jumpToInlinedFinally);
+    }
+
+    /**
      * Callback for entering a LabelNode
      *
      * @param  labelNode the node
--- a/src/jdk/nashorn/internal/objects/Global.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/objects/Global.java	Wed Feb 11 18:55:44 2015 -0800
@@ -41,6 +41,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ConcurrentHashMap;
 import javax.script.ScriptContext;
@@ -463,8 +464,7 @@
             sm.checkPermission(new RuntimePermission(Context.NASHORN_CREATE_GLOBAL));
         }
 
-        // null check on context
-        context.getClass();
+        Objects.requireNonNull(context);
 
         return $nasgenmap$;
     }
@@ -488,7 +488,7 @@
      */
     public static Global instance() {
         final Global global = Context.getGlobal();
-        global.getClass(); // null check
+        Objects.requireNonNull(global);
         return global;
     }
 
@@ -581,13 +581,15 @@
         } else if (obj instanceof String || obj instanceof ConsString) {
             return new NativeString((CharSequence)obj, this);
         } else if (obj instanceof Object[]) { // extension
-            return new NativeArray((Object[])obj);
+            return new NativeArray(ArrayData.allocate((Object[])obj), this);
         } else if (obj instanceof double[]) { // extension
-            return new NativeArray((double[])obj);
+            return new NativeArray(ArrayData.allocate((double[])obj), this);
         } else if (obj instanceof long[]) {
-            return new NativeArray((long[])obj);
+            return new NativeArray(ArrayData.allocate((long[])obj), this);
         } else if (obj instanceof int[]) {
-            return new NativeArray((int[])obj);
+            return new NativeArray(ArrayData.allocate((int[]) obj), this);
+        } else if (obj instanceof ArrayData) {
+            return new NativeArray((ArrayData) obj, this);
         } else {
             // FIXME: more special cases? Map? List?
             return obj;
@@ -1029,14 +1031,19 @@
     }
 
     // builtin prototype accessors
+
+    /**
+     * Get the builtin Object prototype.
+      * @return the object prototype.
+     */
+    public ScriptObject getObjectPrototype() {
+        return ScriptFunction.getPrototype(builtinObject);
+    }
+
     ScriptObject getFunctionPrototype() {
         return ScriptFunction.getPrototype(builtinFunction);
     }
 
-    ScriptObject getObjectPrototype() {
-        return ScriptFunction.getPrototype(builtinObject);
-    }
-
     ScriptObject getArrayPrototype() {
         return ScriptFunction.getPrototype(builtinArray);
     }
--- a/src/jdk/nashorn/internal/objects/NativeArray.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/objects/NativeArray.java	Wed Feb 11 18:55:44 2015 -0800
@@ -120,9 +120,6 @@
         this(ArrayData.allocate(array.length));
 
         ArrayData arrayData = this.getArray();
-        if (array.length > 0) {
-            arrayData.ensure(array.length - 1);
-        }
 
         for (int index = 0; index < array.length; index++) {
             final Object value = array[index];
--- a/src/jdk/nashorn/internal/parser/JSONParser.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/parser/JSONParser.java	Wed Feb 11 18:55:44 2015 -0800
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,38 +25,59 @@
 
 package jdk.nashorn.internal.parser;
 
-import static jdk.nashorn.internal.parser.TokenType.COLON;
-import static jdk.nashorn.internal.parser.TokenType.COMMARIGHT;
-import static jdk.nashorn.internal.parser.TokenType.EOF;
-import static jdk.nashorn.internal.parser.TokenType.ESCSTRING;
-import static jdk.nashorn.internal.parser.TokenType.RBRACE;
-import static jdk.nashorn.internal.parser.TokenType.RBRACKET;
-import static jdk.nashorn.internal.parser.TokenType.STRING;
 import java.util.ArrayList;
 import java.util.List;
-import jdk.nashorn.internal.ir.Expression;
-import jdk.nashorn.internal.ir.LiteralNode;
-import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ObjectNode;
-import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.UnaryNode;
+import jdk.nashorn.internal.codegen.ObjectClassGenerator;
+import jdk.nashorn.internal.objects.Global;
+import jdk.nashorn.internal.runtime.ECMAErrors;
 import jdk.nashorn.internal.runtime.ErrorManager;
+import jdk.nashorn.internal.runtime.JSErrorType;
+import jdk.nashorn.internal.runtime.JSType;
+import jdk.nashorn.internal.runtime.ParserException;
+import jdk.nashorn.internal.runtime.Property;
+import jdk.nashorn.internal.runtime.PropertyMap;
+import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.SpillProperty;
+import jdk.nashorn.internal.runtime.arrays.ArrayData;
+import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
+import jdk.nashorn.internal.scripts.JO;
+
+import static jdk.nashorn.internal.parser.TokenType.STRING;
 
 /**
  * Parses JSON text and returns the corresponding IR node. This is derived from the objectLiteral production of the main parser.
  *
  * See: 15.12.1.2 The JSON Syntactic Grammar
  */
-public class JSONParser extends AbstractParser {
+public class JSONParser {
+
+    final private String source;
+    final private Global global;
+    final int length;
+    int pos = 0;
+
+    private static PropertyMap EMPTY_MAP = PropertyMap.newMap();
+
+    private static final int EOF = -1;
+
+    private static final String TRUE  = "true";
+    private static final String FALSE = "false";
+    private static final String NULL  = "null";
+
+    private static final int STATE_EMPTY          = 0;
+    private static final int STATE_ELEMENT_PARSED = 1;
+    private static final int STATE_COMMA_PARSED   = 2;
 
     /**
      * Constructor
      * @param source  the source
-     * @param errors  the error manager
+     * @param global the global object
      */
-    public JSONParser(final Source source, final ErrorManager errors) {
-        super(source, errors, false, 0);
+    public JSONParser(final String source, final Global global ) {
+        this.source = source;
+        this.global = global;
+        this.length = source.length();
     }
 
     /**
@@ -114,329 +135,409 @@
     }
 
     /**
-     * Public parsed method - start lexing a new token stream for
-     * a JSON script
+     * Public parse method. Parse a string into a JSON object.
      *
-     * @return the JSON literal
+     * @return the parsed JSON Object
      */
-    public Node parse() {
-        stream = new TokenStream();
-
-        lexer = new Lexer(source, stream) {
-
-            @Override
-            protected boolean skipComments() {
-                return false;
-            }
-
-            @Override
-            protected boolean isStringDelimiter(final char ch) {
-                return ch == '\"';
-            }
-
-            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONWhiteSpace
-            @Override
-            protected boolean isWhitespace(final char ch) {
-                return Lexer.isJsonWhitespace(ch);
-            }
-
-            @Override
-            protected boolean isEOL(final char ch) {
-                return Lexer.isJsonEOL(ch);
-            }
+    public Object parse() {
+        final Object value = parseLiteral();
+        skipWhiteSpace();
+        if (pos < length) {
+            throw expectedError(pos, "eof", toString(peek()));
+        }
+        return value;
+    }
 
-            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONNumber
-            @Override
-            protected void scanNumber() {
-                // Record beginning of number.
-                final int startPosition = position;
-                // Assume value is a decimal.
-                TokenType valueType = TokenType.DECIMAL;
-
-                // floating point can't start with a "." with no leading digit before
-                if (ch0 == '.') {
-                    error(Lexer.message("json.invalid.number"), STRING, position, limit);
-                }
-
-                // First digit of number.
-                final int digit = convertDigit(ch0, 10);
-
-                // skip first digit
-                skip(1);
-
-                if (digit != 0) {
-                    // Skip over remaining digits.
-                    while (convertDigit(ch0, 10) != -1) {
-                        skip(1);
-                    }
-                }
-
-                if (ch0 == '.' || ch0 == 'E' || ch0 == 'e') {
-                    // Must be a double.
-                    if (ch0 == '.') {
-                        // Skip period.
-                        skip(1);
+    private Object parseLiteral() {
+        skipWhiteSpace();
 
-                        boolean mantissa = false;
-                        // Skip mantissa.
-                        while (convertDigit(ch0, 10) != -1) {
-                            mantissa = true;
-                            skip(1);
-                        }
-
-                        if (! mantissa) {
-                            // no digit after "."
-                            error(Lexer.message("json.invalid.number"), STRING, position, limit);
-                        }
-                    }
-
-                    // Detect exponent.
-                    if (ch0 == 'E' || ch0 == 'e') {
-                        // Skip E.
-                        skip(1);
-                        // Detect and skip exponent sign.
-                        if (ch0 == '+' || ch0 == '-') {
-                            skip(1);
-                        }
-                        boolean exponent = false;
-                        // Skip exponent.
-                        while (convertDigit(ch0, 10) != -1) {
-                            exponent = true;
-                            skip(1);
-                        }
-
-                        if (! exponent) {
-                            // no digit after "E"
-                            error(Lexer.message("json.invalid.number"), STRING, position, limit);
-                        }
-                    }
-
-                    valueType = TokenType.FLOATING;
-                }
-
-                // Add number token.
-                add(valueType, startPosition);
+        final int c = peek();
+        if (c == EOF) {
+            throw expectedError(pos, "json literal", "eof");
+        }
+        switch (c) {
+        case '{':
+            return parseObject();
+        case '[':
+            return parseArray();
+        case '"':
+            return parseString();
+        case 'f':
+            return parseKeyword(FALSE, Boolean.FALSE);
+        case 't':
+            return parseKeyword(TRUE, Boolean.TRUE);
+        case 'n':
+            return parseKeyword(NULL, null);
+        default:
+            if (isDigit(c) || c == '-') {
+                return parseNumber();
+            } else if (c == '.') {
+                throw numberError(pos);
+            } else {
+                throw expectedError(pos, "json literal", toString(c));
             }
-
-            // ECMA 15.12.1.1 The JSON Lexical Grammar - JSONEscapeCharacter
-            @Override
-            protected boolean isEscapeCharacter(final char ch) {
-                switch (ch) {
-                    case '"':
-                    case '/':
-                    case '\\':
-                    case 'b':
-                    case 'f':
-                    case 'n':
-                    case 'r':
-                    case 't':
-                    // could be unicode escape
-                    case 'u':
-                        return true;
-                    default:
-                        return false;
-                }
-            }
-        };
-
-        k = -1;
-
-        next();
-
-        final Node resultNode = jsonLiteral();
-        expect(EOF);
-
-        return resultNode;
+        }
     }
 
-    @SuppressWarnings("fallthrough")
-    private LiteralNode<?> getStringLiteral() {
-        final LiteralNode<?> literal = getLiteral();
-        final String         str     = (String)literal.getValue();
+    private Object parseObject() {
+        PropertyMap propertyMap = EMPTY_MAP;
+        ArrayData arrayData = ArrayData.EMPTY_ARRAY;
+        final ArrayList<Object> values = new ArrayList<>();
+        int state = STATE_EMPTY;
+
+        assert peek() == '{';
+        pos++;
+
+        while (pos < length) {
+            skipWhiteSpace();
+            final int c = peek();
 
-        for (int i = 0; i < str.length(); i++) {
-            final char ch = str.charAt(i);
-            switch (ch) {
+            switch (c) {
+            case '"':
+                if (state == STATE_ELEMENT_PARSED) {
+                    throw expectedError(pos - 1, ", or }", toString(c));
+                }
+                final String id = parseString();
+                expectColon();
+                final Object value = parseLiteral();
+                final int index = ArrayIndex.getArrayIndex(id);
+                if (ArrayIndex.isValidArrayIndex(index)) {
+                    arrayData = addArrayElement(arrayData, index, value);
+                } else {
+                    propertyMap = addObjectProperty(propertyMap, values, id, value);
+                }
+                state = STATE_ELEMENT_PARSED;
+                break;
+            case ',':
+                if (state != STATE_ELEMENT_PARSED) {
+                    throw error(AbstractParser.message("trailing.comma.in.json"), pos);
+                }
+                state = STATE_COMMA_PARSED;
+                pos++;
+                break;
+            case '}':
+                if (state == STATE_COMMA_PARSED) {
+                    throw error(AbstractParser.message("trailing.comma.in.json"), pos);
+                }
+                pos++;
+                return createObject(propertyMap, values, arrayData);
             default:
-                if (ch > 0x001f) {
-                    break;
-                }
-            case '"':
-            case '\\':
-                throw error(AbstractParser.message("unexpected.token", str));
+                throw expectedError(pos, ", or }", toString(c));
+            }
+        }
+        throw expectedError(pos, ", or }", "eof");
+    }
+
+    private static ArrayData addArrayElement(final ArrayData arrayData, final int index, final Object value) {
+        final long oldLength = arrayData.length();
+        final long longIndex = ArrayIndex.toLongIndex(index);
+        ArrayData newArrayData = arrayData;
+        if (longIndex >= oldLength) {
+            newArrayData = newArrayData.ensure(longIndex);
+            if (longIndex > oldLength) {
+                newArrayData = newArrayData.delete(oldLength, longIndex - 1);
+            }
+        }
+        return newArrayData.set(index, value, false);
+    }
+
+    private static PropertyMap addObjectProperty(final PropertyMap propertyMap, final List<Object> values,
+                                                 final String id, final Object value) {
+        final Property oldProperty = propertyMap.findProperty(id);
+        final Property newProperty;
+        final PropertyMap newMap;
+        final Class<?> type = ObjectClassGenerator.OBJECT_FIELDS_ONLY ? Object.class : getType(value);
+
+        if (oldProperty != null) {
+            values.set(oldProperty.getSlot(), value);
+            newProperty = new SpillProperty(id, 0, oldProperty.getSlot());
+            newProperty.setType(type);
+            newMap = propertyMap.replaceProperty(oldProperty, newProperty);;
+        } else {
+            values.add(value);
+            newProperty = new SpillProperty(id, 0, propertyMap.size());
+            newProperty.setType(type);
+            newMap = propertyMap.addProperty(newProperty);
+        }
+
+        return newMap;
+    }
+
+    private Object createObject(final PropertyMap propertyMap, final List<Object> values, final ArrayData arrayData) {
+        final long[] primitiveSpill = new long[values.size()];
+        final Object[] objectSpill = new Object[values.size()];
+
+        for (final Property property : propertyMap.getProperties()) {
+            if (property.getType() == Object.class) {
+                objectSpill[property.getSlot()] = values.get(property.getSlot());
+            } else {
+                primitiveSpill[property.getSlot()] = ObjectClassGenerator.pack((Number) values.get(property.getSlot()));
             }
         }
 
-        return literal;
+        final ScriptObject object = new JO(propertyMap, primitiveSpill, objectSpill);
+        object.setInitialProto(global.getObjectPrototype());
+        object.setArray(arrayData);
+        return object;
+    }
+
+    private static Class<?> getType(final Object value) {
+        if (value instanceof Integer) {
+            return int.class;
+        } else if (value instanceof Long) {
+            return long.class;
+        } else if (value instanceof Double) {
+            return double.class;
+        } else {
+            return Object.class;
+        }
+    }
+
+    private void expectColon() {
+        skipWhiteSpace();
+        final int n = next();
+        if (n != ':') {
+            throw expectedError(pos - 1, ":", toString(n));
+        }
     }
 
-    /**
-     * Parse a JSON literal from the token stream
-     * @return the JSON literal as a Node
-     */
-    private Expression jsonLiteral() {
-        final long literalToken = token;
+    private Object parseArray() {
+        ArrayData arrayData = ArrayData.EMPTY_ARRAY;
+        int state = STATE_EMPTY;
 
-        switch (type) {
-        case STRING:
-            return getStringLiteral();
-        case ESCSTRING:
-        case DECIMAL:
-        case FLOATING:
-            return getLiteral();
-        case FALSE:
-            next();
-            return LiteralNode.newInstance(literalToken, finish, false);
-        case TRUE:
-            next();
-            return LiteralNode.newInstance(literalToken, finish, true);
-        case NULL:
-            next();
-            return LiteralNode.newInstance(literalToken, finish);
-        case LBRACKET:
-            return arrayLiteral();
-        case LBRACE:
-            return objectLiteral();
-        /*
-         * A.8.1 JSON Lexical Grammar
-         *
-         * JSONNumber :: See 15.12.1.1
-         *    -opt DecimalIntegerLiteral JSONFractionopt ExponentPartopt
-         */
-        case SUB:
-            next();
+        assert peek() == '[';
+        pos++;
 
-            final long realToken = token;
-            final Object value = getValue();
-
-            if (value instanceof Number) {
-                next();
-                return new UnaryNode(literalToken, LiteralNode.newInstance(realToken, finish, (Number)value));
-            }
+        while (pos < length) {
+            skipWhiteSpace();
+            final int c = peek();
 
-            throw error(AbstractParser.message("expected", "number", type.getNameOrType()));
-        default:
-            break;
-        }
-
-        throw error(AbstractParser.message("expected", "json literal", type.getNameOrType()));
-    }
-
-    /**
-     * Parse an array literal from the token stream
-     * @return the array literal as a Node
-     */
-    private LiteralNode<Expression[]> arrayLiteral() {
-        // Unlike JavaScript array literals, elison is not permitted in JSON.
-
-        // Capture LBRACKET token.
-        final long arrayToken = token;
-        // LBRACKET tested in caller.
-        next();
-
-        LiteralNode<Expression[]> result = null;
-        // Prepare to accummulating elements.
-        final List<Expression> elements = new ArrayList<>();
-
-loop:
-        while (true) {
-            switch (type) {
-            case RBRACKET:
-                next();
-                result = LiteralNode.newInstance(arrayToken, finish, elements);
-                break loop;
-
-            case COMMARIGHT:
-                next();
-                // check for trailing comma - not allowed in JSON
-                if (type == RBRACKET) {
-                    throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType()));
+            switch (c) {
+            case ',':
+                if (state != STATE_ELEMENT_PARSED) {
+                    throw error(AbstractParser.message("trailing.comma.in.json"), pos);
                 }
+                state = STATE_COMMA_PARSED;
+                pos++;
                 break;
-
+            case ']':
+                if (state == STATE_COMMA_PARSED) {
+                    throw error(AbstractParser.message("trailing.comma.in.json"), pos);
+                }
+                pos++;
+                return global.wrapAsObject(arrayData);
             default:
-                // Add expression element.
-                elements.add(jsonLiteral());
-                // Comma between array elements is mandatory in JSON.
-                if (type != COMMARIGHT && type != RBRACKET) {
-                   throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType()));
+                if (state == STATE_ELEMENT_PARSED) {
+                    throw expectedError(pos, ", or ]", toString(c));
                 }
+                final long index = arrayData.length();
+                arrayData = arrayData.ensure(index).set((int) index, parseLiteral(), true);
+                state = STATE_ELEMENT_PARSED;
                 break;
             }
         }
 
-        return result;
+        throw expectedError(pos, ", or ]", "eof");
     }
 
-    /**
-     * Parse an object literal from the token stream
-     * @return the object literal as a Node
-     */
-    private ObjectNode objectLiteral() {
-        // Capture LBRACE token.
-        final long objectToken = token;
-        // LBRACE tested in caller.
-        next();
+    private String parseString() {
+        // String buffer is only instantiated if string contains escape sequences.
+        int start = ++pos;
+        StringBuilder sb = null;
 
-        // Prepare to accumulate elements.
-        final List<PropertyNode> elements = new ArrayList<>();
+        while (pos < length) {
+            final int c = next();
+            if (c <= 0x1f) {
+                // Characters < 0x1f are not allowed in JSON strings.
+                throw syntaxError(pos, "String contains control character");
 
-        // Create a block for the object literal.
-loop:
-        while (true) {
-            switch (type) {
-            case RBRACE:
-                next();
-                break loop;
+            } else if (c == '\\') {
+                if (sb == null) {
+                    sb = new StringBuilder(pos - start + 16);
+                }
+                sb.append(source, start, pos - 1);
+                sb.append(parseEscapeSequence());
+                start = pos;
 
-            case COMMARIGHT:
-                next();
-                // check for trailing comma - not allowed in JSON
-                if (type == RBRACE) {
-                    throw error(AbstractParser.message("trailing.comma.in.json", type.getNameOrType()));
+            } else if (c == '"') {
+                if (sb != null) {
+                    sb.append(source, start, pos - 1);
+                    return sb.toString();
                 }
-                break;
-
-            default:
-                // Get and add the next property.
-                final PropertyNode property = propertyAssignment();
-                elements.add(property);
-
-                // Comma between property assigments is mandatory in JSON.
-                if (type != RBRACE && type != COMMARIGHT) {
-                    throw error(AbstractParser.message("expected", ", or }", type.getNameOrType()));
-                }
-                break;
+                return source.substring(start, pos - 1);
             }
         }
 
-        // Construct new object literal.
-        return new ObjectNode(objectToken, finish, elements);
+        throw error(Lexer.message("missing.close.quote"), pos, length);
+    }
+
+    private char parseEscapeSequence() {
+        final int c = next();
+        switch (c) {
+        case '"':
+            return '"';
+        case '\\':
+            return '\\';
+        case '/':
+            return '/';
+        case 'b':
+            return '\b';
+        case 'f':
+            return '\f';
+        case 'n':
+            return '\n';
+        case 'r':
+            return '\r';
+        case 't':
+            return '\t';
+        case 'u':
+            return parseUnicodeEscape();
+        default:
+            throw error(Lexer.message("invalid.escape.char"), pos - 1, length);
+        }
+    }
+
+    private char parseUnicodeEscape() {
+        return (char) (parseHexDigit() << 12 | parseHexDigit() << 8 | parseHexDigit() << 4 | parseHexDigit());
+    }
+
+    private int parseHexDigit() {
+        final int c = next();
+        if (c >= '0' && c <= '9') {
+            return c - '0';
+        } else if (c >= 'A' && c <= 'F') {
+            return c + 10 - 'A';
+        } else if (c >= 'a' && c <= 'f') {
+            return c + 10 - 'a';
+        }
+        throw error(Lexer.message("invalid.hex"), pos - 1, length);
+    }
+
+    private boolean isDigit(final int c) {
+        return c >= '0' && c <= '9';
+    }
+
+    private void skipDigits() {
+        while (pos < length) {
+            final int c = peek();
+            if (!isDigit(c)) {
+                break;
+            }
+            pos++;
+        }
     }
 
-    /**
-     * Parse a property assignment from the token stream
-     * @return the property assignment as a Node
-     */
-    private PropertyNode propertyAssignment() {
-        // Capture firstToken.
-        final long propertyToken = token;
-        LiteralNode<?> name = null;
+    private Number parseNumber() {
+        final int start = pos;
+        int c = next();
+
+        if (c == '-') {
+            c = next();
+        }
+        if (!isDigit(c)) {
+            throw numberError(start);
+        }
+        // no more digits allowed after 0
+        if (c != '0') {
+            skipDigits();
+        }
 
-        if (type == STRING) {
-            name = getStringLiteral();
-        } else if (type == ESCSTRING) {
-            name = getLiteral();
+        // fraction
+        if (peek() == '.') {
+            pos++;
+            if (!isDigit(next())) {
+                throw numberError(pos - 1);
+            }
+            skipDigits();
+        }
+
+        // exponent
+        c = peek();
+        if (c == 'e' || c == 'E') {
+            pos++;
+            c = next();
+            if (c == '-' || c == '+') {
+                c = next();
+            }
+            if (!isDigit(c)) {
+                throw numberError(pos - 1);
+            }
+            skipDigits();
         }
 
-        if (name != null) {
-            expect(COLON);
-            final Expression value = jsonLiteral();
-            return new PropertyNode(propertyToken, value.getFinish(), name, value, null, null);
+        final double d = Double.parseDouble(source.substring(start, pos));
+        if (JSType.isRepresentableAsInt(d)) {
+            return (int) d;
+        } else if (JSType.isRepresentableAsLong(d)) {
+            return (long) d;
+        }
+        return d;
+    }
+
+    private Object parseKeyword(final String keyword, final Object value) {
+        if (!source.regionMatches(pos, keyword, 0, keyword.length())) {
+            throw expectedError(pos, "json literal", "ident");
         }
+        pos += keyword.length();
+        return value;
+    }
 
-        // Raise an error.
-        throw error(AbstractParser.message("expected", "string", type.getNameOrType()));
+    private int peek() {
+        if (pos >= length) {
+            return -1;
+        }
+        return source.charAt(pos);
+    }
+
+    private int next() {
+        final int next = peek();
+        pos++;
+        return next;
     }
 
+    private void skipWhiteSpace() {
+        while (pos < length) {
+            switch (peek()) {
+            case '\t':
+            case '\r':
+            case '\n':
+            case ' ':
+                pos++;
+                break;
+            default:
+                return;
+            }
+        }
+    }
+
+    private static String toString(final int c) {
+        return c == EOF ? "eof" : String.valueOf((char) c);
+    }
+
+    ParserException error(final String message, final int start, final int length) throws ParserException {
+        final long token     = Token.toDesc(STRING, start, length);
+        final int  pos       = Token.descPosition(token);
+        final Source src     = Source.sourceFor("<json>", source);
+        final int  lineNum   = src.getLine(pos);
+        final int  columnNum = src.getColumn(pos);
+        final String formatted = ErrorManager.format(message, src, lineNum, columnNum, token);
+        return new ParserException(JSErrorType.SYNTAX_ERROR, formatted, src, lineNum, columnNum, token);
+    }
+
+    private ParserException error(final String message, final int start) {
+        return error(message, start, length);
+    }
+
+    private ParserException numberError(final int start) {
+        return error(Lexer.message("json.invalid.number"), start);
+    }
+
+    private ParserException expectedError(final int start, final String expected, final String found) {
+        return error(AbstractParser.message("expected", expected, found), start);
+    }
+
+    private ParserException syntaxError(final int start, final String reason) {
+        final String message = ECMAErrors.getMessage("syntax.error.invalid.json", reason);
+        return error(message, start);
+    }
 }
--- a/src/jdk/nashorn/internal/parser/Lexer.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/parser/Lexer.java	Wed Feb 11 18:55:44 2015 -0800
@@ -93,9 +93,6 @@
     private static final String SPACETAB = " \t";  // ASCII space and tab
     private static final String LFCR     = "\n\r"; // line feed and carriage return (ctrl-m)
 
-    private static final String JSON_WHITESPACE_EOL = LFCR;
-    private static final String JSON_WHITESPACE     = SPACETAB + LFCR;
-
     private static final String JAVASCRIPT_WHITESPACE_EOL =
         LFCR +
         "\u2028" + // line separator
@@ -385,24 +382,6 @@
     }
 
     /**
-     * Test whether a char is valid JSON whitespace
-     * @param ch a char
-     * @return true if valid JSON whitespace
-     */
-    public static boolean isJsonWhitespace(final char ch) {
-        return JSON_WHITESPACE.indexOf(ch) != -1;
-    }
-
-    /**
-     * Test whether a char is valid JSON end of line
-     * @param ch a char
-     * @return true if valid JSON end of line
-     */
-    public static boolean isJsonEOL(final char ch) {
-        return JSON_WHITESPACE_EOL.indexOf(ch) != -1;
-    }
-
-    /**
      * Test if char is a string delimiter, e.g. '\' or '"'.  Also scans exec
      * strings ('`') in scripting mode.
      * @param ch a char
--- a/src/jdk/nashorn/internal/runtime/Context.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/Context.java	Wed Feb 11 18:55:44 2015 -0800
@@ -60,6 +60,7 @@
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
@@ -904,7 +905,7 @@
      * @throw SecurityException if not accessible
      */
     private static void checkPackageAccess(final SecurityManager sm, final String fullName) {
-        sm.getClass(); // null check
+        Objects.requireNonNull(sm);
         final int index = fullName.lastIndexOf('.');
         if (index != -1) {
             final String pkgName = fullName.substring(0, index);
--- a/src/jdk/nashorn/internal/runtime/JSONFunctions.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/JSONFunctions.java	Wed Feb 11 18:55:44 2015 -0800
@@ -25,19 +25,11 @@
 
 package jdk.nashorn.internal.runtime;
 
-import static jdk.nashorn.internal.runtime.Source.sourceFor;
-
 import java.lang.invoke.MethodHandle;
 import java.util.Iterator;
 import java.util.concurrent.Callable;
-import jdk.nashorn.internal.ir.LiteralNode;
-import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ObjectNode;
-import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.UnaryNode;
 import jdk.nashorn.internal.objects.Global;
 import jdk.nashorn.internal.parser.JSONParser;
-import jdk.nashorn.internal.parser.TokenType;
 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
 import jdk.nashorn.internal.runtime.linker.Bootstrap;
 
@@ -78,20 +70,18 @@
      * @return Object representation of JSON text given
      */
     public static Object parse(final Object text, final Object reviver) {
-        final String     str     = JSType.toString(text);
-        final JSONParser parser  = new JSONParser(sourceFor("<json>", str), new Context.ThrowErrorManager());
-
-        Node node;
+        final String     str    = JSType.toString(text);
+        final Global     global = Context.getGlobal();
+        final JSONParser parser = new JSONParser(str, global);
+        final Object     value;
 
         try {
-            node = parser.parse();
+            value = parser.parse();
         } catch (final ParserException e) {
             throw ECMAErrors.syntaxError(e, "invalid.json", e.getMessage());
         }
 
-        final Global global = Context.getGlobal();
-        final Object unfiltered = convertNode(global, node);
-        return applyReviver(global, unfiltered, reviver);
+        return applyReviver(global, value, reviver);
     }
 
     // -- Internals only below this point
@@ -137,61 +127,6 @@
         }
     }
 
-    // Converts IR node to runtime value
-    private static Object convertNode(final Global global, final Node node) {
-        if (node instanceof LiteralNode) {
-            // check for array literal
-            if (node.tokenType() == TokenType.ARRAY) {
-                assert node instanceof LiteralNode.ArrayLiteralNode;
-                final Node[] elements = ((LiteralNode.ArrayLiteralNode)node).getValue();
-
-                // NOTE: We cannot use LiteralNode.isNumericArray() here as that
-                // method uses symbols of element nodes. Since we don't do lower
-                // pass, there won't be any symbols!
-                if (isNumericArray(elements)) {
-                    final double[] values = new double[elements.length];
-                    int   index = 0;
-
-                    for (final Node elem : elements) {
-                        values[index++] = JSType.toNumber(convertNode(global, elem));
-                    }
-                    return global.wrapAsObject(values);
-                }
-
-                final Object[] values = new Object[elements.length];
-                int   index = 0;
-
-                for (final Node elem : elements) {
-                    values[index++] = convertNode(global, elem);
-                }
-
-                return global.wrapAsObject(values);
-            }
-
-            return ((LiteralNode<?>)node).getValue();
-
-        } else if (node instanceof ObjectNode) {
-            final ObjectNode   objNode  = (ObjectNode) node;
-            final ScriptObject object   = global.newObject();
-
-            for (final PropertyNode pNode: objNode.getElements()) {
-                final Node         valueNode = pNode.getValue();
-
-                final String name = pNode.getKeyName();
-                final Object value = convertNode(global, valueNode);
-                setPropertyValue(object, name, value);
-            }
-
-            return object;
-        } else if (node instanceof UnaryNode) {
-            // UnaryNode used only to represent negative number JSON value
-            final UnaryNode unaryNode = (UnaryNode)node;
-            return -((LiteralNode<?>)unaryNode.getExpression()).getNumber();
-        } else {
-            return null;
-        }
-    }
-
     // add a new property if does not exist already, or else set old property
     private static void setPropertyValue(final ScriptObject sobj, final String name, final Object value) {
         final int index = ArrayIndex.getArrayIndex(name);
@@ -207,14 +142,4 @@
         }
     }
 
-    // does the given IR node represent a numeric array?
-    private static boolean isNumericArray(final Node[] values) {
-        for (final Node node : values) {
-            if (node instanceof LiteralNode && ((LiteralNode<?>)node).getValue() instanceof Number) {
-                continue;
-            }
-            return false;
-        }
-        return true;
-    }
 }
--- a/src/jdk/nashorn/internal/runtime/PropertyMap.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/PropertyMap.java	Wed Feb 11 18:55:44 2015 -0800
@@ -486,7 +486,7 @@
      *
      * @return New {@link PropertyMap} with {@link Property} replaced.
      */
-    PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
+    public PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
         if (listeners != null) {
             listeners.propertyModified(oldProperty, newProperty);
         }
--- a/src/jdk/nashorn/internal/runtime/ScriptLoader.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/ScriptLoader.java	Wed Feb 11 18:55:44 2015 -0800
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.runtime;
 
 import java.security.CodeSource;
+import java.util.Objects;
 
 /**
  * Responsible for loading script generated classes.
@@ -69,8 +70,7 @@
      * @return Installed class.
      */
     synchronized Class<?> installClass(final String name, final byte[] data, final CodeSource cs) {
-        // null check
-        cs.getClass();
+        Objects.requireNonNull(cs);
         return defineClass(name, data, 0, data.length, cs);
     }
 }
--- a/src/jdk/nashorn/internal/runtime/ScriptObject.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/ScriptObject.java	Wed Feb 11 18:55:44 2015 -0800
@@ -722,8 +722,12 @@
     public void defineOwnProperty(final int index, final Object value) {
         assert isValidArrayIndex(index) : "invalid array index";
         final long longIndex = ArrayIndex.toLongIndex(index);
-        doesNotHaveEnsureDelete(longIndex, getArray().length(), false);
-        setArray(getArray().ensure(longIndex).set(index,value, false));
+        final long oldLength = getArray().length();
+        if (longIndex >= oldLength) {
+            setArray(getArray().ensure(longIndex));
+            doesNotHaveEnsureDelete(longIndex, oldLength, false);
+        }
+        setArray(getArray().set(index,value, false));
     }
 
     private void checkIntegerKey(final String key) {
--- a/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/ArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -118,7 +118,7 @@
                     return new SparseArrayData(this, safeIndex + 1);
                 }
                 //known to fit in int
-                return toRealArrayData((int)safeIndex).ensure(safeIndex);
+                return toRealArrayData((int)safeIndex);
            }
            return this;
         }
@@ -497,7 +497,9 @@
     public abstract ArrayData shiftRight(final int by);
 
     /**
-     * Ensure that the given index exists and won't fail subsequent
+     * Ensure that the given index exists and won't fail in a subsequent access.
+     * If {@code safeIndex} is equal or greater than the current length the length is
+     * updated to {@code safeIndex + 1}.
      *
      * @param safeIndex the index to ensure wont go out of bounds
      * @return new array data (or same)
--- a/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/ContinuousArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -57,7 +57,7 @@
     }
 
     /**
-     * Check if we can put one more element at the end of this continous
+     * Check if we can put one more element at the end of this continuous
      * array without reallocating, or if we are overwriting an already
      * allocated element
      *
--- a/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/IntArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -221,7 +221,9 @@
             final int newLength = ArrayData.nextSize((int)safeIndex);
             array = Arrays.copyOf(array, newLength);
         }
-        setLength(safeIndex + 1);
+        if (safeIndex >= length()) {
+            setLength(safeIndex + 1);
+        }
         return this;
     }
 
--- a/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/LongArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -157,7 +157,9 @@
             final int newLength = ArrayData.nextSize((int)safeIndex);
             array = Arrays.copyOf(array, newLength);
         }
-        setLength(safeIndex + 1);
+        if (safeIndex >= length()) {
+            setLength(safeIndex + 1);
+        }
         return this;
     }
 
--- a/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/NumberArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -139,7 +139,9 @@
             final int newLength = ArrayData.nextSize((int)safeIndex);
             array = Arrays.copyOf(array, newLength); //todo fill with nan or never accessed?
         }
-        setLength(safeIndex + 1);
+        if (safeIndex >= length()) {
+            setLength(safeIndex + 1);
+        }
         return this;
 
     }
--- a/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/ObjectArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -123,7 +123,9 @@
             final int newLength = ArrayData.nextSize((int)safeIndex);
             array = Arrays.copyOf(array, newLength); //fill with undefined or OK? TODO
         }
-        setLength(safeIndex + 1);
+        if (safeIndex >= length()) {
+            setLength(safeIndex + 1);
+        }
         return this;
     }
 
--- a/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/arrays/SparseArrayData.java	Wed Feb 11 18:55:44 2015 -0800
@@ -135,10 +135,17 @@
 
     @Override
     public ArrayData ensure(final long safeIndex) {
+        // Usually #ensure only needs to be called if safeIndex is greater or equal current length.
+        // SparseArrayData is an exception as an index smaller than our current length may still
+        // exceed the underlying ArrayData's capacity. Because of this, SparseArrayData invokes
+        // its ensure method internally in various places where other ArrayData subclasses don't,
+        // making it safe for outside uses to only call ensure(safeIndex) if safeIndex >= length.
         if (safeIndex < maxDenseLength && underlying.length() <= safeIndex) {
             underlying = underlying.ensure(safeIndex);
         }
-        setLength(Math.max(safeIndex + 1, length()));
+        if (safeIndex >= length()) {
+            setLength(safeIndex + 1);
+        }
         return this;
     }
 
--- a/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapter.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/linker/JavaSuperAdapter.java	Wed Feb 11 18:55:44 2015 -0800
@@ -25,6 +25,8 @@
 
 package jdk.nashorn.internal.runtime.linker;
 
+import java.util.Objects;
+
 /**
  * Represents a an adapter for invoking superclass methods on an adapter instance generated by
  * {@code JavaAdapterBytecodeGenerator}. Note that objects of this class are just wrappers around the adapter instances,
@@ -34,7 +36,7 @@
     private final Object adapter;
 
     JavaSuperAdapter(final Object adapter) {
-        adapter.getClass(); // NPE check
+        Objects.requireNonNull(adapter);
         this.adapter = adapter;
     }
 
--- a/src/jdk/nashorn/internal/runtime/options/Options.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/runtime/options/Options.java	Wed Feb 11 18:55:44 2015 -0800
@@ -42,6 +42,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.MissingResourceException;
+import java.util.Objects;
 import java.util.PropertyPermission;
 import java.util.ResourceBundle;
 import java.util.StringTokenizer;
@@ -143,7 +144,7 @@
      * @return true if set to true, default value if unset or set to false
      */
     public static boolean getBooleanProperty(final String name, final Boolean defValue) {
-        name.getClass(); // null check
+        Objects.requireNonNull(name);
         if (!name.startsWith("nashorn.")) {
             throw new IllegalArgumentException(name);
         }
@@ -184,7 +185,7 @@
      * @return string property if set or default value
      */
     public static String getStringProperty(final String name, final String defValue) {
-        name.getClass(); // null check
+        Objects.requireNonNull(name);
         if (! name.startsWith("nashorn.")) {
             throw new IllegalArgumentException(name);
         }
@@ -211,7 +212,7 @@
      * @return integer property if set or default value
      */
     public static int getIntProperty(final String name, final int defValue) {
-        name.getClass(); // null check
+        Objects.requireNonNull(name);
         if (! name.startsWith("nashorn.")) {
             throw new IllegalArgumentException(name);
         }
--- a/src/jdk/nashorn/internal/scripts/JO.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/src/jdk/nashorn/internal/scripts/JO.java	Wed Feb 11 18:55:44 2015 -0800
@@ -25,7 +25,6 @@
 
 package jdk.nashorn.internal.scripts;
 
-import jdk.nashorn.internal.codegen.SpillObjectCreator;
 import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.ScriptObject;
 
@@ -64,8 +63,9 @@
     }
 
     /**
-     * Constructor that takes a pre-initialized spill pool. Used for
-     * by {@link SpillObjectCreator} for intializing object literals
+     * Constructor that takes a pre-initialized spill pool. Used by
+     * {@link jdk.nashorn.internal.codegen.SpillObjectCreator} and
+     * {@link jdk.nashorn.internal.parser.JSONParser} for initializing object literals
      *
      * @param map            property map
      * @param primitiveSpill primitive spill pool
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/examples/json-parser-micro.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+function bench() {
+    var start = Date.now();
+    for (var i = 0; i < 2000; i++) {
+        JSON.parse(String(json));
+    }
+    print("1000 iterations in", Date.now() - start, "millis");
+}
+
+var json = '[\
+  {\
+    "_id": "54ca34171d3ade49782294c8",\
+    "index": 0,\
+    "guid": "ed0e74d5-ac63-47b6-8938-1750abab5770",\
+    "isActive": false,\
+    "balance": "$1,996.19",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 39,\
+    "eyeColor": "green",\
+    "name": "Rose Graham",\
+    "gender": "male",\
+    "company": "PRIMORDIA",\
+    "email": "rosegraham@primordia.com",\
+    "phone": "+1 (985) 600-3551",\
+    "address": "364 Melba Court, Succasunna, Texas, 8393",\
+    "about": "Sunt commodo cillum occaecat velit eu eiusmod ex eiusmod sunt deserunt nulla proident incididunt. Incididunt ullamco Lorem elit do culpa esse do ex dolor aliquip labore. Ullamco velit laboris incididunt dolor. Nostrud dolor sint pariatur fugiat ullamco exercitation. Eu laboris do cupidatat eiusmod incididunt mollit occaecat voluptate.",\
+    "registered": "2014-03-13T12:05:14 -01:00",\
+    "latitude": 18.55665,\
+    "longitude": 81.641001,\
+    "tags": [\
+      "sint",\
+      "Lorem",\
+      "veniam",\
+      "quis",\
+      "proident",\
+      "consectetur",\
+      "consequat"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Evangelina Morgan"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Saunders Snyder"\
+      },\
+      {\
+        "id": 2,\
+        "name": "Walker Wood"\
+      }\
+    ],\
+    "greeting": "Hello, Rose Graham! You have 1 unread messages.",\
+    "favoriteFruit": "strawberry"\
+  },\
+  {\
+    "_id": "54ca34176790c4c60fcae085",\
+    "index": 1,\
+    "guid": "9dc42e4c-b58f-4d92-a2ee-968d2b627d92",\
+    "isActive": true,\
+    "balance": "$3,832.97",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 40,\
+    "eyeColor": "brown",\
+    "name": "Delaney Cherry",\
+    "gender": "male",\
+    "company": "INJOY",\
+    "email": "delaneycherry@injoy.com",\
+    "phone": "+1 (807) 463-2295",\
+    "address": "470 Hale Avenue, Mulberry, District Of Columbia, 5455",\
+    "about": "Deserunt sit cupidatat elit Lorem excepteur ex. Magna officia minim cupidatat nulla enim deserunt. Amet ex in tempor commodo consequat non ad qui elit cupidatat esse labore sint.",\
+    "registered": "2014-03-27T23:06:33 -01:00",\
+    "latitude": -4.984238,\
+    "longitude": 116.039285,\
+    "tags": [\
+      "minim",\
+      "velit",\
+      "aute",\
+      "minim",\
+      "id",\
+      "enim",\
+      "enim"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Barrera Flowers"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Leann Larson"\
+      },\
+      {\
+        "id": 2,\
+        "name": "Latoya Petty"\
+      }\
+    ],\
+    "greeting": "Hello, Delaney Cherry! You have 2 unread messages.",\
+    "favoriteFruit": "strawberry"\
+  },\
+  {\
+    "_id": "54ca3417920666f00c54bfc4",\
+    "index": 2,\
+    "guid": "f91e08f8-1598-49bc-a08b-bb48f0cc1751",\
+    "isActive": true,\
+    "balance": "$2,932.84",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 28,\
+    "eyeColor": "brown",\
+    "name": "Mosley Hammond",\
+    "gender": "male",\
+    "company": "AQUACINE",\
+    "email": "mosleyhammond@aquacine.com",\
+    "phone": "+1 (836) 598-2591",\
+    "address": "879 Columbia Place, Seymour, Montana, 4897",\
+    "about": "Sunt laborum incididunt et elit in deserunt deserunt irure enim ea qui non. Minim nisi sint aute veniam reprehenderit veniam reprehenderit. Elit enim eu voluptate eu cupidatat nulla ea incididunt exercitation voluptate ut aliquip excepteur ipsum. Consequat anim fugiat irure Lorem anim consectetur est.",\
+    "registered": "2014-07-27T05:05:58 -02:00",\
+    "latitude": -43.608015,\
+    "longitude": -38.33894,\
+    "tags": [\
+      "proident",\
+      "incididunt",\
+      "eiusmod",\
+      "anim",\
+      "consectetur",\
+      "qui",\
+      "excepteur"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Hanson Davidson"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Autumn Kaufman"\
+      },\
+      {\
+        "id": 2,\
+        "name": "Tammy Foley"\
+      }\
+    ],\
+    "greeting": "Hello, Mosley Hammond! You have 4 unread messages.",\
+    "favoriteFruit": "apple"\
+  },\
+  {\
+    "_id": "54ca341753b67572a2b04935",\
+    "index": 3,\
+    "guid": "3377416b-43a2-4f9e-ada3-2479e13b44b8",\
+    "isActive": false,\
+    "balance": "$3,821.54",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 31,\
+    "eyeColor": "green",\
+    "name": "Mueller Barrett",\
+    "gender": "male",\
+    "company": "GROK",\
+    "email": "muellerbarrett@grok.com",\
+    "phone": "+1 (890) 535-2834",\
+    "address": "571 Norwood Avenue, Westwood, Arkansas, 2164",\
+    "about": "Occaecat est sunt commodo ut ex excepteur elit nulla velit minim commodo commodo esse. Lorem quis eu minim consectetur. Cupidatat cupidatat consequat sit eu ex non quis nulla veniam sint enim excepteur. Consequat minim duis do do minim fugiat minim elit laborum ut velit. Occaecat laboris veniam sint reprehenderit.",\
+    "registered": "2014-07-18T17:15:35 -02:00",\
+    "latitude": 10.746577,\
+    "longitude": -160.266041,\
+    "tags": [\
+      "reprehenderit",\
+      "veniam",\
+      "sint",\
+      "commodo",\
+      "exercitation",\
+      "cillum",\
+      "sunt"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Summers Finch"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Tracie Mcdaniel"\
+      },\
+      {\
+        "id": 2,\
+        "name": "Ayers Patrick"\
+      }\
+    ],\
+    "greeting": "Hello, Mueller Barrett! You have 7 unread messages.",\
+    "favoriteFruit": "apple"\
+  },\
+  {\
+    "_id": "54ca34172775ab9615db0d1d",\
+    "index": 4,\
+    "guid": "a3102a3e-3f08-4df3-b5b5-62eff985d5ca",\
+    "isActive": true,\
+    "balance": "$3,962.27",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 34,\
+    "eyeColor": "green",\
+    "name": "Patrick Foster",\
+    "gender": "male",\
+    "company": "QUAREX",\
+    "email": "patrickfoster@quarex.com",\
+    "phone": "+1 (805) 577-2362",\
+    "address": "640 Richards Street, Roberts, American Samoa, 5530",\
+    "about": "Aute occaecat occaecat ad eiusmod esse aliqua ullamco minim. Exercitation aute ut ex nostrud deserunt laboris officia amet enim do. Cillum officia laborum occaecat eiusmod reprehenderit ex et aliqua minim elit ex aliqua mollit. Occaecat dolor in fugiat laboris aliquip nisi ad voluptate duis eiusmod ad do.",\
+    "registered": "2014-07-22T16:45:35 -02:00",\
+    "latitude": 6.609025,\
+    "longitude": -5.357026,\
+    "tags": [\
+      "ea",\
+      "ut",\
+      "excepteur",\
+      "enim",\
+      "ad",\
+      "non",\
+      "sit"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Duncan Lewis"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Alyce Benton"\
+      },\
+      {\
+        "id": 2,\
+        "name": "Angelique Larsen"\
+      }\
+    ],\
+    "greeting": "Hello, Patrick Foster! You have 1 unread messages.",\
+    "favoriteFruit": "strawberry"\
+  },\
+  {\
+    "_id": "54ca3417a190f26fef815f6d",\
+    "index": 5,\
+    "guid": "c09663dd-bb0e-45a4-960c-232c0e8a9486",\
+    "isActive": false,\
+    "balance": "$1,871.12",\
+    "picture": "http://placehold.it/32x32",\
+    "age": 20,\
+    "eyeColor": "blue",\
+    "name": "Foreman Chaney",\
+    "gender": "male",\
+    "company": "DEMINIMUM",\
+    "email": "foremanchaney@deminimum.com",\
+    "phone": "+1 (966) 523-2182",\
+    "address": "960 Granite Street, Sunnyside, Tennessee, 1097",\
+    "about": "Adipisicing nisi qui id sit incididunt aute exercitation veniam consequat ipsum sit irure. Aute officia commodo Lorem consequat. Labore exercitation consequat voluptate deserunt consequat do est fugiat nisi eu dolor minim id ea.",\
+    "registered": "2015-01-21T00:18:00 -01:00",\
+    "latitude": -69.841726,\
+    "longitude": 121.809383,\
+    "tags": [\
+      "laboris",\
+      "sunt",\
+      "exercitation",\
+      "enim",\
+      "anim",\
+      "excepteur",\
+      "tempor"\
+    ],\
+    "friends": [\
+      {\
+        "id": 0,\
+        "name": "Espinoza Johnston"\
+      },\
+      {\
+        "id": 1,\
+        "name": "Doreen Holder"\
+      },\
+      {\
+        "id": 2,\
+        "name": "William Ellison"\
+      }\
+    ],\
+    "greeting": "Hello, Foreman Chaney! You have 5 unread messages.",\
+    "favoriteFruit": "strawberry"\
+  }\
+]';
+
+for (var i = 0; i < 100; i++) {
+    bench();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8062141.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8062141: Various performance issues parsing JSON
+ *
+ * @test
+ * @run
+ */
+
+function testJson(json) {
+    try {
+        print(JSON.stringify(JSON.parse(json)));
+    } catch (error) {
+        print(error);
+    }
+}
+
+testJson('"\\u003f"');
+testJson('"\\u0"');
+testJson('"\\u0"');
+testJson('"\\u00"');
+testJson('"\\u003"');
+testJson('"\\u003x"');
+testJson('"\\"');
+testJson('"');
+testJson('+1');
+testJson('-1');
+testJson('1.');
+testJson('.1');
+testJson('01');
+testJson('1e');
+testJson('1e0');
+testJson('1a');
+testJson('1e+');
+testJson('1e-');
+testJson('0.0e+0');
+testJson('0.0e-0');
+testJson('[]');
+testJson('[ 1 ]');
+testJson('[1,]');
+testJson('[ 1 , 2 ]');
+testJson('[1, 2');
+testJson('{}');
+testJson('{ "a" : "b" }');
+testJson('{ "a" : "b" ');
+testJson('{ "a" : }');
+testJson('true');
+testJson('tru');
+testJson('true1');
+testJson('false');
+testJson('fals');
+testJson('falser');
+testJson('null');
+testJson('nul');
+testJson('null0');
+testJson('{} 0');
+testJson('{} a');
+testJson('[] 0');
+testJson('[] a');
+testJson('1 0');
+testJson('1 a');
+testJson('["a":true]');
+testJson('{"a",truer}');
+testJson('{"a":truer}');
+testJson('[1, 2, 3]');
+testJson('[9223372036854774000, 9223372036854775000, 9223372036854776000]');
+testJson('[1.1, 1.2, 1.3]');
+testJson('[1, 1.2, 9223372036854776000, null, true]');
+testJson('{ "a" : "string" , "b": 1 , "c" : 1.2 , "d" : 9223372036854776000 , "e" : null , "f" : true }');
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8062141.js.EXPECTED	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,120 @@
+"?"
+SyntaxError: Invalid JSON: <json>:1:4 Invalid hex digit
+"\u0"
+    ^
+SyntaxError: Invalid JSON: <json>:1:4 Invalid hex digit
+"\u0"
+    ^
+SyntaxError: Invalid JSON: <json>:1:5 Invalid hex digit
+"\u00"
+     ^
+SyntaxError: Invalid JSON: <json>:1:6 Invalid hex digit
+"\u003"
+      ^
+SyntaxError: Invalid JSON: <json>:1:6 Invalid hex digit
+"\u003x"
+      ^
+SyntaxError: Invalid JSON: <json>:1:3 Missing close quote
+"\"
+   ^
+SyntaxError: Invalid JSON: <json>:1:1 Missing close quote
+"
+ ^
+SyntaxError: Invalid JSON: <json>:1:0 Expected json literal but found +
++1
+^
+-1
+SyntaxError: Invalid JSON: <json>:1:2 Invalid JSON number format
+1.
+  ^
+SyntaxError: Invalid JSON: <json>:1:0 Invalid JSON number format
+.1
+^
+SyntaxError: Invalid JSON: <json>:1:1 Expected eof but found 1
+01
+ ^
+SyntaxError: Invalid JSON: <json>:1:2 Invalid JSON number format
+1e
+  ^
+1
+SyntaxError: Invalid JSON: <json>:1:1 Expected eof but found a
+1a
+ ^
+SyntaxError: Invalid JSON: <json>:1:3 Invalid JSON number format
+1e+
+   ^
+SyntaxError: Invalid JSON: <json>:1:3 Invalid JSON number format
+1e-
+   ^
+0
+0
+[]
+[1]
+SyntaxError: Invalid JSON: <json>:1:3 Trailing comma is not allowed in JSON
+[1,]
+   ^
+[1,2]
+SyntaxError: Invalid JSON: <json>:1:5 Expected , or ] but found eof
+[1, 2
+     ^
+{}
+{"a":"b"}
+SyntaxError: Invalid JSON: <json>:1:12 Expected , or } but found eof
+{ "a" : "b" 
+            ^
+SyntaxError: Invalid JSON: <json>:1:8 Expected json literal but found }
+{ "a" : }
+        ^
+true
+SyntaxError: Invalid JSON: <json>:1:0 Expected json literal but found ident
+tru
+^
+SyntaxError: Invalid JSON: <json>:1:4 Expected eof but found 1
+true1
+    ^
+false
+SyntaxError: Invalid JSON: <json>:1:0 Expected json literal but found ident
+fals
+^
+SyntaxError: Invalid JSON: <json>:1:5 Expected eof but found r
+falser
+     ^
+null
+SyntaxError: Invalid JSON: <json>:1:0 Expected json literal but found ident
+nul
+^
+SyntaxError: Invalid JSON: <json>:1:4 Expected eof but found 0
+null0
+    ^
+SyntaxError: Invalid JSON: <json>:1:3 Expected eof but found 0
+{} 0
+   ^
+SyntaxError: Invalid JSON: <json>:1:3 Expected eof but found a
+{} a
+   ^
+SyntaxError: Invalid JSON: <json>:1:3 Expected eof but found 0
+[] 0
+   ^
+SyntaxError: Invalid JSON: <json>:1:3 Expected eof but found a
+[] a
+   ^
+SyntaxError: Invalid JSON: <json>:1:2 Expected eof but found 0
+1 0
+  ^
+SyntaxError: Invalid JSON: <json>:1:2 Expected eof but found a
+1 a
+  ^
+SyntaxError: Invalid JSON: <json>:1:4 Expected , or ] but found :
+["a":true]
+    ^
+SyntaxError: Invalid JSON: <json>:1:4 Expected : but found ,
+{"a",truer}
+    ^
+SyntaxError: Invalid JSON: <json>:1:9 Expected , or } but found r
+{"a":truer}
+         ^
+[1,2,3]
+[9223372036854773800,9223372036854774800,9223372036854776000]
+[1.1,1.2,1.3]
+[1,1.2,9223372036854776000,null,true]
+{"a":"string","b":1,"c":1.2,"d":9223372036854776000,"e":null,"f":true}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8066232.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8066232: problem with conditional catch compilation
+ *
+ * @test
+ * @run
+ */
+
+(function () { 
+    try {
+        x;
+    } catch(e if 1) {
+    }
+})()
+
+print("SUCCESS");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8066232.js.EXPECTED	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,1 @@
+SUCCESS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8067139.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2014 Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8067139: Finally blocks inlined incorrectly
+ *
+ * @test
+ * @run
+ */
+
+// Test case for JDK-8067139
+// as well as for JDK-8030198 which is a duplicate.
+(function(){
+    var catchCount = 0; 
+    try {
+        (function (){
+            try { 
+                return; 
+            } catch(x) { 
+                ++catchCount;
+            } finally { 
+                throw 0; 
+            } 
+        })();
+        Assert.fail(); // must throw
+    } catch(e) {
+        Assert.assertEquals(e, 0); // threw 0
+        Assert.assertEquals(catchCount, 0); // inner catch never executed
+    }
+})();
+
+// Test case for JDK-8048862 which is a duplicate of this bug
+var ret = (function(o) { 
+    try{
+        with(o) {
+            return x;
+        }
+    } finally {
+        try { 
+            return x;
+        } catch(e) {
+            Assert.assertTrue(e instanceof ReferenceError);
+            return 2;
+        }
+    }
+})({x: 1});
+Assert.assertEquals(ret, 2); // executed the catch block
+
+// Test cases for JDK-8066231 that is a duplicate of this bug
+// Case 1
+(function (){ try { Object; } catch(x if x >>>=0) { throw x2; } finally { } })();
+// Case 2
+try {
+    (function (){ try { return; } catch(x) { return x ^= 0; } finally { throw 0; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, 0); // threw 0
+}
+// Case 3
+try {
+    (function (){ try { return; } catch(x) { return x ^= Object; } finally { throw Object; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, Object); // threw Object
+}
+// Case from comment
+try {
+    (function () { try { Object } catch(x) { (x=y); return; } finally { throw Object; } })();
+    Assert.fail();
+} catch(e) {
+    Assert.assertEquals(e, Object); // threw Object
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8068872.js	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8068872:  Nashorn JSON.parse drops numeric keys
+ *
+ * @test
+ * @run
+ */
+
+print(JSON.stringify(JSON.parse('{"3": 1, "5": "a"}')));
+print(JSON.stringify(JSON.parse('{"5": 1, "3": "a"}')));
+print(JSON.stringify(JSON.parse('{"0": 1, "4294967294": "a"}')));
+print(JSON.stringify(JSON.parse('{"4294967294": 1, "0": "a"}')));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/script/basic/JDK-8068872.js.EXPECTED	Wed Feb 11 18:55:44 2015 -0800
@@ -0,0 +1,4 @@
+{"3":1,"5":"a"}
+{"3":"a","5":1}
+{"0":1,"4294967294":"a"}
+{"0":"a","4294967294":1}
--- a/test/script/basic/NASHORN-623.js.EXPECTED	Wed Feb 11 12:18:58 2015 -0800
+++ b/test/script/basic/NASHORN-623.js.EXPECTED	Wed Feb 11 18:55:44 2015 -0800
@@ -1,3 +1,3 @@
-SyntaxError: Invalid JSON: <json>:1:12 Expected number but found ident
+SyntaxError: Invalid JSON: <json>:1:11 Invalid JSON number format
 { "test" : -xxx }
-            ^
+           ^
--- a/test/src/jdk/nashorn/api/scripting/ScopeTest.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/test/src/jdk/nashorn/api/scripting/ScopeTest.java	Wed Feb 11 18:55:44 2015 -0800
@@ -706,4 +706,74 @@
         }
     }
 
+    // @bug 8071678: NashornScriptEngine returns javax.script.ScriptContext instance
+    // with get/setAttribute methods insonsistent for GLOBAL_SCOPE
+    @Test
+    public void testGlobalScopeSearch() throws Exception {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        final ScriptContext c = e.getContext();
+        c.setAttribute("name1234", "value", ScriptContext.GLOBAL_SCOPE);
+        assertEquals(c.getAttribute("name1234"), "value");
+        assertEquals(c.getAttributesScope("name1234"),
+            ScriptContext.GLOBAL_SCOPE);
+    }
+
+    // @bug 8071594: NashornScriptEngine returns javax.script.ScriptContext instance
+    // which doesn't completely conform to the spec regarding exceptions throwing
+    @Test
+    public void testScriptContext_NPE_IAE() throws Exception {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        final ScriptContext c = e.getContext();
+        try {
+            c.getAttribute("");
+            throw new AssertionError("should have thrown IAE");
+        } catch (IllegalArgumentException iae1) {}
+
+        try {
+            c.getAttribute(null);
+            throw new AssertionError("should have thrown NPE");
+        } catch (NullPointerException npe1) {}
+
+        try {
+            c.getAttribute("", ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown IAE");
+        } catch (IllegalArgumentException iae2) {}
+
+        try {
+            c.getAttribute(null, ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown NPE");
+        } catch (NullPointerException npe2) {}
+
+        try {
+            c.removeAttribute("", ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown IAE");
+        } catch (IllegalArgumentException iae3) {}
+
+        try {
+            c.removeAttribute(null, ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown NPE");
+        } catch (NullPointerException npe3) {}
+
+        try {
+            c.setAttribute("", "value", ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown IAE");
+        } catch (IllegalArgumentException iae4) {}
+
+        try {
+            c.setAttribute(null, "value", ScriptContext.ENGINE_SCOPE);
+            throw new AssertionError("should have thrown NPE");
+        } catch (NullPointerException npe4) {}
+
+        try {
+            c.getAttributesScope("");
+            throw new AssertionError("should have thrown IAE");
+        } catch (IllegalArgumentException iae5) {}
+
+        try {
+            c.getAttributesScope(null);
+            throw new AssertionError("should have thrown NPE");
+        } catch (NullPointerException npe5) {}
+    }
 }
--- a/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java	Wed Feb 11 12:18:58 2015 -0800
+++ b/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java	Wed Feb 11 18:55:44 2015 -0800
@@ -721,6 +721,15 @@
         assertTrue(invoked.get());
     }
 
+    @Test
+    public void testLengthOnArrayLikeObjects() throws Exception {
+        final ScriptEngine e = new ScriptEngineManager().getEngineByName("nashorn");
+        final Object val = e.eval("var arr = { length: 1, 0: 1}; arr.length");
+
+        assertTrue(Number.class.isAssignableFrom(val.getClass()));
+        assertTrue(((Number)val).intValue() == 1);
+    }
+
     // @bug JDK-8068603: NashornScriptEngine.put/get() impls don't conform to NPE, IAE spec assertions
     @Test
     public void illegalBindingsValuesTest() throws Exception {
@@ -843,6 +852,17 @@
         }
     }
 
+    // @bug 8071989: NashornScriptEngine returns javax.script.ScriptContext instance
+    // with insonsistent get/remove methods behavior for undefined attributes
+    @Test
+    public void testScriptContextGetRemoveUndefined() throws Exception {
+        final ScriptEngineManager manager = new ScriptEngineManager();
+        final ScriptEngine e = manager.getEngineByName("nashorn");
+        final ScriptContext ctx = e.getContext();
+        assertNull(ctx.getAttribute("undefinedname", ScriptContext.ENGINE_SCOPE));
+        assertNull(ctx.removeAttribute("undefinedname", ScriptContext.ENGINE_SCOPE));
+    }
+
     private static void checkProperty(final ScriptEngine e, final String name)
         throws ScriptException {
         final String value = System.getProperty(name);