8057980: let & const: remaining issues with lexical scoping
authorhannesw
Thu, 27 Nov 2014 16:42:53 +0100
changeset 27817 56f6161c3e55
parent 27816 c6c53c5adc51
child 27818 6bf1e00c7229
8057980: let & const: remaining issues with lexical scoping Reviewed-by: lagergren, attila
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LoopNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/VarNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/WhileNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/ParserContextSwitchNode.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/arrays/ArrayData.java
nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties
nashorn/test/script/basic/es6/for-let.js
nashorn/test/script/basic/es6/for-let.js.EXPECTED
nashorn/test/script/basic/es6/let-const-statement-context.js
nashorn/test/script/basic/es6/let-const-statement-context.js.EXPECTED
nashorn/test/script/basic/es6/let-const-switch.js
nashorn/test/script/basic/es6/let-const-switch.js.EXPECTED
nashorn/test/script/basic/es6/let-load.js
nashorn/test/script/basic/es6/let-load.js.EXPECTED
nashorn/test/script/basic/es6/let_const_closure.js.EXPECTED
nashorn/test/script/basic/es6/lexical-toplevel.js.EXPECTED
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Thu Nov 27 16:42:53 2014 +0100
@@ -189,7 +189,7 @@
      * @param body the body of the FunctionNode we are entering
      */
     private void acceptDeclarations(final FunctionNode functionNode, final Block body) {
-        // This visitor will assign symbol to all declared variables, except "var" declarations in for loop initializers.
+        // This visitor will assign symbol to all declared variables.
         body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
             @Override
             protected boolean enterDefault(final Node node) {
@@ -200,16 +200,17 @@
 
             @Override
             public Node leaveVarNode(final VarNode varNode) {
-                if (varNode.isStatement()) {
-                    final IdentNode ident  = varNode.getName();
-                    final Block block = varNode.isBlockScoped() ? getLexicalContext().getCurrentBlock() : body;
-                    final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags());
-                    if (varNode.isFunctionDeclaration()) {
-                        symbol.setIsFunctionDeclaration();
-                    }
-                    return varNode.setName(ident.setSymbol(symbol));
+                final IdentNode ident  = varNode.getName();
+                final boolean blockScoped = varNode.isBlockScoped();
+                if (blockScoped && lc.inUnprotectedSwitchContext()) {
+                    throwUnprotectedSwitchError(varNode);
                 }
-                return varNode;
+                final Block block = blockScoped ? lc.getCurrentBlock() : body;
+                final Symbol symbol = defineSymbol(block, ident.getName(), ident, varNode.getSymbolFlags());
+                if (varNode.isFunctionDeclaration()) {
+                    symbol.setIsFunctionDeclaration();
+                }
+                return varNode.setName(ident.setSymbol(symbol));
             }
         });
     }
@@ -1048,6 +1049,15 @@
         return !(units == null || units.isEmpty());
     }
 
+    private void throwUnprotectedSwitchError(final VarNode varNode) {
+        // Block scoped declarations in switch statements without explicit blocks should be declared
+        // in a common block that contains all the case clauses. We cannot support this without a
+        // fundamental rewrite of how switch statements are handled (case nodes contain blocks and are
+        // directly contained by switch node). As a temporary solution we throw a reference error here.
+        final String msg = ECMAErrors.getMessage("syntax.error.unprotected.switch.declaration", varNode.isLet() ? "let" : "const");
+        throwParserException(msg, varNode);
+    }
+
     private void throwParserException(final String message, final Node origin) {
         if (origin == null) {
             throw new ParserException(message);
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Nov 27 16:42:53 2014 +0100
@@ -3264,6 +3264,13 @@
             emitContinueLabel(continueLabel, liveLocalsOnContinue);
         }
 
+        if (loopNode.hasPerIterationScope() && lc.getParentBlock().needsScope()) {
+            // ES6 for loops with LET init need a new scope for each iteration. We just create a shallow copy here.
+            method.loadCompilerConstant(SCOPE);
+            method.invoke(virtualCallNoLookup(ScriptObject.class, "copy", ScriptObject.class));
+            method.storeCompilerConstant(SCOPE);
+        }
+
         if(method.isReachable()) {
             if(modify != null) {
                 lineNumber(loopNode);
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java	Thu Nov 27 16:42:53 2014 +0100
@@ -525,7 +525,7 @@
 
         if (isAlwaysTrue(test)) {
             //turn it into a for node without a test.
-            final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, ForNode.IS_FOR).accept(this);
+            final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, 0).accept(this);
             lc.replace(whileNode, forNode);
             return forNode;
         }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/ForNode.java	Thu Nov 27 16:42:53 2014 +0100
@@ -45,14 +45,14 @@
     /** Iterator symbol. */
     private Symbol iterator;
 
-    /** Is this a normal for loop? */
-    public static final int IS_FOR      = 1 << 0;
-
     /** Is this a normal for in loop? */
-    public static final int IS_FOR_IN   = 1 << 1;
+    public static final int IS_FOR_IN           = 1 << 0;
 
     /** Is this a normal for each in loop? */
-    public static final int IS_FOR_EACH = 1 << 2;
+    public static final int IS_FOR_EACH         = 1 << 1;
+
+    /** Does this loop need a per-iteration scope because its init contain a LET declaration? */
+    public static final int PER_ITERATION_SCOPE = 1 << 2;
 
     private final int flags;
 
@@ -264,4 +264,9 @@
     JoinPredecessor setLocalVariableConversionChanged(final LexicalContext lc, final LocalVariableConversion conversion) {
         return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes, conversion));
     }
+
+    @Override
+    public boolean hasPerIterationScope() {
+        return (flags & PER_ITERATION_SCOPE) != 0;
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LexicalContext.java	Thu Nov 27 16:42:53 2014 +0100
@@ -597,6 +597,20 @@
         throw new AssertionError(target + " was expected in lexical context " + LexicalContext.this + " but wasn't");
     }
 
+    /**
+     * Checks whether the current context is inside a switch statement without explicit blocks (curly braces).
+     * @return true if in unprotected switch statement
+     */
+    public boolean inUnprotectedSwitchContext() {
+        for (int i = sp; i > 0; i--) {
+            final LexicalContextNode next = stack[i];
+            if (next instanceof Block) {
+                return stack[i - 1] instanceof SwitchNode;
+            }
+        }
+        return false;
+    }
+
     @Override
     public String toString() {
         final StringBuffer sb = new StringBuffer();
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LoopNode.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/LoopNode.java	Thu Nov 27 16:42:53 2014 +0100
@@ -177,4 +177,10 @@
      * @return new loop node if changed otherwise the same
      */
     public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes);
+
+    /**
+     * Does this loop have a LET declaration and hence require a per-iteration scope?
+     * @return true if a per-iteration scope is required.
+     */
+    public abstract boolean hasPerIterationScope();
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/VarNode.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/VarNode.java	Thu Nov 27 16:42:53 2014 +0100
@@ -45,19 +45,16 @@
     /** Is this a var statement (as opposed to a "var" in a for loop statement) */
     private final int flags;
 
-    /** Flag that determines if this function node is a statement */
-    public static final int IS_STATEMENT                 = 1 << 0;
-
     /** Flag for ES6 LET declaration */
-    public static final int IS_LET                       = 1 << 1;
+    public static final int IS_LET                       = 1 << 0;
 
     /** Flag for ES6 CONST declaration */
-    public static final int IS_CONST                     = 1 << 2;
+    public static final int IS_CONST                     = 1 << 1;
 
     /** Flag that determines if this is the last function declaration in a function
      *  This is used to micro optimize the placement of return value assignments for
      *  a program node */
-    public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 3;
+    public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 2;
 
     /**
      * Constructor
@@ -69,7 +66,7 @@
      * @param init       init node or null if just a declaration
      */
     public VarNode(final int lineNumber, final long token, final int finish, final IdentNode name, final Expression init) {
-        this(lineNumber, token, finish, name, init, IS_STATEMENT);
+        this(lineNumber, token, finish, name, init, 0);
     }
 
     private VarNode(final VarNode varNode, final IdentNode name, final Expression init, final int flags) {
@@ -260,14 +257,6 @@
     }
 
     /**
-     * Returns true if this is a var statement (as opposed to a var initializer in a for loop).
-     * @return true if this is a var statement (as opposed to a var initializer in a for loop).
-     */
-    public boolean isStatement() {
-        return (flags & IS_STATEMENT) != 0;
-    }
-
-    /**
      * Returns true if this is a function declaration.
      * @return true if this is a function declaration.
      */
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/WhileNode.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/WhileNode.java	Thu Nov 27 16:42:53 2014 +0100
@@ -150,4 +150,9 @@
         }
         return test == null;
     }
+
+    @Override
+    public boolean hasPerIterationScope() {
+        return false;
+    }
 }
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/Parser.java	Thu Nov 27 16:42:53 2014 +0100
@@ -554,7 +554,7 @@
         // Set up new block. Captures first token.
         final ParserContextBlockNode newBlock = newBlock();
         try {
-            statement();
+            statement(false, false, true);
         } finally {
             restoreBlock(newBlock);
         }
@@ -770,7 +770,7 @@
 
                 try {
                     // Get the next element.
-                    statement(true, allowPropertyFunction);
+                    statement(true, allowPropertyFunction, false);
                     allowPropertyFunction = false;
 
                     // check for directive prologues
@@ -860,13 +860,15 @@
      * Parse any of the basic statement types.
      */
     private void statement() {
-        statement(false, false);
+        statement(false, false, false);
     }
 
     /**
      * @param topLevel does this statement occur at the "top level" of a script or a function?
+     * @param allowPropertyFunction allow property "get" and "set" functions?
+     * @param singleStatement are we in a single statement context?
      */
-    private void statement(final boolean topLevel, final boolean allowPropertyFunction) {
+    private void statement(final boolean topLevel, final boolean allowPropertyFunction, final boolean singleStatement) {
         if (type == FUNCTION) {
             // As per spec (ECMA section 12), function declarations as arbitrary statement
             // is not "portable". Implementation can issue a warning or disallow the same.
@@ -930,6 +932,9 @@
             break;
         default:
             if (useBlockScope() && (type == LET || type == CONST)) {
+                if (singleStatement) {
+                    throw error(AbstractParser.message("expected.stmt", type.getName() + " declaration"), token);
+                }
                 variableStatement(type, true);
                 break;
             }
@@ -1055,7 +1060,7 @@
         next();
 
         final List<VarNode> vars = new ArrayList<>();
-        int varFlags = VarNode.IS_STATEMENT;
+        int varFlags = 0;
         if (varType == LET) {
             varFlags |= VarNode.IS_LET;
         } else if (varType == CONST) {
@@ -1200,7 +1205,6 @@
         final int startLine = start;
         final ParserContextBlockNode outer = useBlockScope() ? newBlock() : null;
 
-
         // Create FOR node, capturing FOR token.
         final ParserContextLoopNode forNode = new ParserContextLoopNode();
         lc.push(forNode);
@@ -1228,19 +1232,22 @@
 
             switch (type) {
             case VAR:
-                // Var statements captured in for outer block.
+                // Var declaration captured in for outer block.
                 vars = variableStatement(type, false);
                 break;
             case SEMICOLON:
                 break;
             default:
                 if (useBlockScope() && (type == LET || type == CONST)) {
-                    // LET/CONST captured in container block created above.
+                    if (type == LET) {
+                        flags |= ForNode.PER_ITERATION_SCOPE;
+                    }
+                    // LET/CONST declaration captured in container block created above.
                     vars = variableStatement(type, false);
                     break;
                 }
                 if (env._const_as_var && type == CONST) {
-                    // Var statements captured in for outer block.
+                    // Var declaration captured in for outer block.
                     vars = variableStatement(TokenType.VAR, false);
                     break;
                 }
@@ -1316,21 +1323,22 @@
             body = getStatement();
         } finally {
             lc.pop(forNode);
-            if (vars != null) {
-                for (final VarNode var : vars) {
-                    appendStatement(var);
-                }
-            }
-            if (body != null) {
-                appendStatement(new ForNode(forLine, forToken, body.getFinish(), body, (forNode.getFlags() | flags), init, test, modify));
+        }
+
+        if (vars != null) {
+            for (final VarNode var : vars) {
+                appendStatement(var);
             }
-            if (outer != null) {
-                restoreBlock(outer);
-                appendStatement(new BlockStatement(startLine, new Block(
-                        outer.getToken(),
-                        body.getFinish(),
-                        outer.getStatements())));
-            }
+        }
+        if (body != null) {
+            appendStatement(new ForNode(forLine, forToken, body.getFinish(), body, (forNode.getFlags() | flags), init, test, modify));
+        }
+        if (outer != null) {
+            restoreBlock(outer);
+            appendStatement(new BlockStatement(startLine, new Block(
+                    outer.getToken(),
+                    body.getFinish(),
+                    outer.getStatements())));
         }
     }
 
@@ -1364,9 +1372,10 @@
             body = getStatement();
         } finally {
             lc.pop(whileNode);
-            if (body != null){
-              appendStatement(new WhileNode(whileLine, whileToken, body.getFinish(), false, test, body));
-            }
+        }
+
+        if (body != null) {
+            appendStatement(new WhileNode(whileLine, whileToken, body.getFinish(), false, test, body));
         }
     }
 
@@ -1408,8 +1417,9 @@
             }
         } finally {
             lc.pop(doWhileNode);
-            appendStatement(new WhileNode(doLine, doToken, finish, true, test, body));
         }
+
+        appendStatement(new WhileNode(doLine, doToken, finish, true, test, body));
     }
 
     /**
@@ -1607,17 +1617,12 @@
             throw error(AbstractParser.message("strict.no.with"), withToken);
         }
 
-        Expression expression = null;
-        Block body = null;
-        try {
-            expect(LPAREN);
-            expression = expression();
-            expect(RPAREN);
-            body = getStatement();
-        } finally {
-            appendStatement(new WithNode(withLine, withToken, finish, expression, body));
-        }
-
+        expect(LPAREN);
+        final Expression expression = expression();
+        expect(RPAREN);
+        final Block body = getStatement();
+
+        appendStatement(new WithNode(withLine, withToken, finish, expression, body));
     }
 
     /**
@@ -1706,8 +1711,9 @@
             next();
         } finally {
             lc.pop(switchNode);
-            appendStatement(new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCase));
         }
+
+        appendStatement(new SwitchNode(switchLine, switchToken, finish, expression, cases, defaultCase));
     }
 
     /**
@@ -1738,10 +1744,9 @@
         } finally {
             assert lc.peek() instanceof ParserContextLabelNode;
             lc.pop(labelNode);
-            if (ident != null){
-              appendStatement(new LabelNode(line, labelToken, finish, ident.getName(), body));
-            }
         }
+
+        appendStatement(new LabelNode(line, labelToken, finish, ident.getName(), body));
     }
 
     /**
@@ -2725,12 +2730,9 @@
                 functionBody);
 
         if (isStatement) {
-            int varFlags = VarNode.IS_STATEMENT;
-            if (!topLevel && useBlockScope()) {
-                // mark ES6 block functions as lexically scoped
-                varFlags |= VarNode.IS_LET;
-            }
-            final VarNode varNode = new VarNode(functionLine, functionToken, finish, name, function, varFlags);
+            // mark ES6 block functions as lexically scoped
+            final int     varFlags = (topLevel || !useBlockScope()) ? 0 : VarNode.IS_LET;
+            final VarNode varNode  = new VarNode(functionLine, functionToken, finish, name, function, varFlags);
             if (topLevel) {
                 functionDeclarations.add(varNode);
             } else if (useBlockScope()) {
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/ParserContextSwitchNode.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/parser/ParserContextSwitchNode.java	Thu Nov 27 16:42:53 2014 +0100
@@ -25,7 +25,7 @@
 package jdk.nashorn.internal.parser;
 
 /**
- * A ParserContextNode that represents a SwithcNode that is currently being parsed
+ * A ParserContextNode that represents a SwitchNode that is currently being parsed
  */
 class ParserContextSwitchNode extends ParserContextBaseNode implements ParserContextBreakableNode {
 
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Thu Nov 27 16:42:53 2014 +0100
@@ -46,6 +46,8 @@
 import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
+import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isScopeFlag;
+import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.isStrictFlag;
 import static jdk.nashorn.internal.runtime.linker.NashornGuards.explicitInstanceOfCheck;
 
 import java.lang.invoke.MethodHandle;
@@ -98,7 +100,7 @@
  * </ul>
  */
 
-public abstract class ScriptObject implements PropertyAccess {
+public abstract class ScriptObject implements PropertyAccess, Cloneable {
     /** __proto__ special property name inside object literals. ES6 draft. */
     public static final String PROTO_PROPERTY_NAME   = "__proto__";
 
@@ -2202,6 +2204,9 @@
 
         if (find != null) {
             if (!find.getProperty().isWritable() && !NashornCallSiteDescriptor.isDeclaration(desc)) {
+                if (NashornCallSiteDescriptor.isScope(desc) && find.getProperty().isLexicalBinding()) {
+                    throw typeError("assign.constant", name); // Overwriting ES6 const should throw also in non-strict mode.
+                }
                 // Existing, non-writable property
                 return createEmptySetMethod(desc, explicitInstanceOfCheck, "property.not.writable", true);
             }
@@ -3103,7 +3108,7 @@
     private boolean doesNotHaveEnsureLength(final long longIndex, final long oldLength, final int callSiteFlags) {
         if (longIndex >= oldLength) {
             if (!isExtensible()) {
-                if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) {
+                if (isStrictFlag(callSiteFlags)) {
                     throw typeError("object.non.extensible", JSType.toString(longIndex), ScriptRuntime.safeToString(this));
                 }
                 return true;
@@ -3127,7 +3132,7 @@
         final long oldLength = getArray().length();
         final long longIndex = ArrayIndex.toLongIndex(index);
         if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) {
-            final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags);
+            final boolean strict = isStrictFlag(callSiteFlags);
             setArray(getArray().set(index, value, strict));
             doesNotHaveEnsureDelete(longIndex, oldLength, strict);
         }
@@ -3137,7 +3142,7 @@
         final long oldLength = getArray().length();
         final long longIndex = ArrayIndex.toLongIndex(index);
         if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) {
-            final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags);
+            final boolean strict = isStrictFlag(callSiteFlags);
             setArray(getArray().set(index, value, strict));
             doesNotHaveEnsureDelete(longIndex, oldLength, strict);
         }
@@ -3147,7 +3152,7 @@
         final long oldLength = getArray().length();
         final long longIndex = ArrayIndex.toLongIndex(index);
         if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) {
-            final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags);
+            final boolean strict = isStrictFlag(callSiteFlags);
             setArray(getArray().set(index, value, strict));
             doesNotHaveEnsureDelete(longIndex, oldLength, strict);
         }
@@ -3157,7 +3162,7 @@
         final long oldLength = getArray().length();
         final long longIndex = ArrayIndex.toLongIndex(index);
         if (!doesNotHaveCheckArrayKeys(longIndex, value, callSiteFlags) && !doesNotHaveEnsureLength(longIndex, oldLength, callSiteFlags)) {
-            final boolean strict = NashornCallSiteDescriptor.isStrictFlag(callSiteFlags);
+            final boolean strict = isStrictFlag(callSiteFlags);
             setArray(getArray().set(index, value, strict));
             doesNotHaveEnsureDelete(longIndex, oldLength, strict);
         }
@@ -3178,7 +3183,7 @@
         invalidateGlobalConstant(key);
 
         if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty)) {
-            final boolean isScope = NashornCallSiteDescriptor.isScopeFlag(callSiteFlags);
+            final boolean isScope = isScopeFlag(callSiteFlags);
             // If the start object of the find is not this object it means the property was found inside a
             // 'with' statement expression (see WithObject.findProperty()). In this case we forward the 'set'
             // to the 'with' object.
@@ -3199,16 +3204,19 @@
 
         if (f != null) {
             if (!f.getProperty().isWritable()) {
-                if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) {
+                if (isScopeFlag(callSiteFlags) && f.getProperty().isLexicalBinding()) {
+                    throw typeError("assign.constant", key); // Overwriting ES6 const should throw also in non-strict mode.
+                }
+                if (isStrictFlag(callSiteFlags)) {
                     throw typeError("property.not.writable", key, ScriptRuntime.safeToString(this));
                 }
                 return;
             }
 
-            f.setValue(value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags));
+            f.setValue(value, isStrictFlag(callSiteFlags));
 
         } else if (!isExtensible()) {
-            if (NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)) {
+            if (isStrictFlag(callSiteFlags)) {
                 throw typeError("object.non.extensible", key, ScriptRuntime.safeToString(this));
             }
         } else {
@@ -3235,7 +3243,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3255,7 +3263,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3275,7 +3283,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3295,7 +3303,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3314,7 +3322,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3333,7 +3341,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3352,7 +3360,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3371,7 +3379,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3390,7 +3398,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3409,7 +3417,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3428,7 +3436,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3447,7 +3455,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3465,7 +3473,7 @@
         if (isValidArrayIndex(index)) {
             if (getArray().has(index)) {
                 final ArrayData data = getArray();
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3483,7 +3491,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3502,7 +3510,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3521,7 +3529,7 @@
         if (isValidArrayIndex(index)) {
             final ArrayData data = getArray();
             if (data.has(index)) {
-                setArray(data.set(index, value, NashornCallSiteDescriptor.isStrictFlag(callSiteFlags)));
+                setArray(data.set(index, value, isStrictFlag(callSiteFlags)));
             } else {
                 doesNotHave(index, value, callSiteFlags);
             }
@@ -3686,6 +3694,29 @@
     }
 
     /**
+     * Return a shallow copy of this ScriptObject.
+     * @return a shallow copy.
+     */
+    public final ScriptObject copy() {
+        try {
+            return clone();
+        } catch (final CloneNotSupportedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    protected ScriptObject clone() throws CloneNotSupportedException {
+        final ScriptObject clone = (ScriptObject) super.clone();
+        if (objectSpill != null) {
+            clone.objectSpill = objectSpill.clone();
+            clone.primitiveSpill = primitiveSpill.clone();
+        }
+        clone.arrayData = arrayData.copy();
+        return clone;
+    }
+
+    /**
      * Make a new UserAccessorProperty property. getter and setter functions are stored in
      * this ScriptObject and slot values are used in property object.
      *
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/arrays/ArrayData.java	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/arrays/ArrayData.java	Thu Nov 27 16:42:53 2014 +0100
@@ -61,9 +61,9 @@
     /**
      * Length of the array data. Not necessarily length of the wrapped array.
      * This is private to ensure that no one in a subclass is able to touch the length
-     * without going through {@link setLength}. This is used to implement
+     * without going through {@link #setLength}. This is used to implement
      * {@link LengthNotWritableFilter}s, ensuring that there are no ways past
-     * a {@link setLength} function replaced by a nop
+     * a {@link #setLength} function replaced by a nop
      */
     private long length;
 
@@ -79,11 +79,7 @@
      */
     private static class UntouchedArrayData extends ContinuousArrayData {
         private UntouchedArrayData() {
-            this(0);
-        }
-
-        private UntouchedArrayData(final int length) {
-            super(length);
+            super(0);
         }
 
         private ArrayData toRealArrayData() {
@@ -100,7 +96,8 @@
 
         @Override
         public ContinuousArrayData copy() {
-            return new UntouchedArrayData((int)length());
+            assert length() == 0;
+            return this;
         }
 
         @Override
@@ -246,7 +243,7 @@
         public Class<?> getBoxedElementType() {
             return Integer.class;
         }
-    };
+    }
 
     /**
      * Constructor
--- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/resources/Messages.properties	Thu Nov 27 16:42:53 2014 +0100
@@ -116,6 +116,7 @@
 type.error.cannot.convert.to.interface=object {0} cannot be converted to {1} due to "{2}"
 type.error.array.reduce.invalid.init=invalid initialValue for Array.prototype.reduce
 type.error.array.reduceright.invalid.init=invalid initialValue for Array.prototype.reduceRight
+type.error.assign.constant=Assignment to constant "{0}"
 type.error.cannot.get.default.string=Cannot get default string value
 type.error.cannot.get.default.number=Cannot get default number value
 type.error.cant.apply.with.to.null=Cannot apply "with" to null
@@ -166,6 +167,7 @@
 syntax.error.strict.cant.delete=cannot delete "{0}" in strict mode
 syntax.error.redeclare.variable=Variable "{0}" has already been declared
 syntax.error.assign.constant=Assignment to constant "{0}"
+syntax.error.unprotected.switch.declaration=Unsupported {0} declaration in unprotected switch statement
 
 io.error.cant.write=cannot write "{0}"
 config.error.no.dest=no destination directory supplied
--- a/nashorn/test/script/basic/es6/for-let.js	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/for-let.js	Thu Nov 27 16:42:53 2014 +0100
@@ -39,3 +39,40 @@
 } catch (e) {
     print(e);
 }
+
+let a = [];
+
+for (let i = 0; i < 10; i++) {
+    a.push(function() { print(i); });
+}
+
+a.forEach(function(f) { f(); });
+
+a = [];
+
+for (let i = 0; i < 10; i++) {
+    if (i == 5) {
+        i = "foo";
+    }
+    a.push(function() { print(i); });
+}
+
+a.forEach(function(f) { f(); });
+
+try {
+    print(i);
+} catch (e) {
+    print(e);
+}
+
+a = [];
+
+for (let i = 0; i < 20; i++) {
+    if (i % 2 == 1) {
+        i += 2;
+        continue;
+    }
+    a.push(function() { print(i); });
+}
+
+a.forEach(function(f) { f(); });
--- a/nashorn/test/script/basic/es6/for-let.js.EXPECTED	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/for-let.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -9,3 +9,25 @@
 8
 9
 ReferenceError: "i" is not defined
+0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+0
+1
+2
+3
+4
+foo
+ReferenceError: "i" is not defined
+0
+4
+8
+12
+16
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/es6/let-const-statement-context.js	Thu Nov 27 16:42:53 2014 +0100
@@ -0,0 +1,49 @@
+/*
+ * 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-8057980: let & const: remaining issues with lexical scoping
+ *
+ * @test
+ * @run
+ * @option --language=es6
+ */
+
+function tryEval(s) {
+    try {
+        eval(s);
+    } catch (e) {
+        print(String(e).replace(/\\/g, "/"));
+    }
+}
+
+tryEval('if (true) let x = 1;');
+tryEval('if (true) const x = 1;');
+tryEval('while (true) let x = 1;');
+tryEval('while (true) const x = 1;');
+tryEval('for (;;) let x = 1;');
+tryEval('for (;;) const x = 1;');
+tryEval('do let x = 1; while (true);');
+tryEval('do const x = 1; while (true);');
+tryEval('with (y) const x = 1;');
+tryEval('with (y) let x = 1;');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/es6/let-const-statement-context.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -0,0 +1,30 @@
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:10 Expected statement but found let declaration
+if (true) let x = 1;
+          ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:10 Expected statement but found const declaration
+if (true) const x = 1;
+          ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:13 Expected statement but found let declaration
+while (true) let x = 1;
+             ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:13 Expected statement but found const declaration
+while (true) const x = 1;
+             ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:9 Expected statement but found let declaration
+for (;;) let x = 1;
+         ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:9 Expected statement but found const declaration
+for (;;) const x = 1;
+         ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:3 Expected statement but found let declaration
+do let x = 1; while (true);
+   ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:3 Expected statement but found const declaration
+do const x = 1; while (true);
+   ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:9 Expected statement but found const declaration
+with (y) const x = 1;
+         ^
+SyntaxError: test/script/basic/es6/let-const-statement-context.js#34:8<eval>:1:9 Expected statement but found let declaration
+with (y) let x = 1;
+         ^
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/es6/let-const-switch.js	Thu Nov 27 16:42:53 2014 +0100
@@ -0,0 +1,45 @@
+/*
+ * 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-8057980: let & const: remaining issues with lexical scoping
+ *
+ * @test
+ * @run
+ * @option --language=es6
+ */
+
+function tryEval(s) {
+    try {
+        eval(s);
+    } catch (e) {
+        print(String(e).replace(/\\/g, "/"));
+    }
+}
+
+tryEval('var x = 0; switch (x) { case 0: { let   x = 1; print(x); } case 1: { let   x = 2; print(x); }} print(x);');
+tryEval('var x = 0; switch (x) { case 0: { const x = 1; print(x); } case 1: { const x = 2; print(x); }} print(x);');
+
+// TODO: the following should not throw
+tryEval('switch (x) { case 0: let x = 1; }');
+tryEval('switch (x) { case 0: const x = 1; }');
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/es6/let-const-switch.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -0,0 +1,12 @@
+1
+2
+0
+1
+2
+0
+SyntaxError: test/script/basic/es6/let-const-switch.js#34:8<eval>:1:25 Unsupported let declaration in unprotected switch statement
+switch (x) { case 0: let x = 1; }
+                         ^
+SyntaxError: test/script/basic/es6/let-const-switch.js#34:8<eval>:1:27 Unsupported const declaration in unprotected switch statement
+switch (x) { case 0: const x = 1; }
+                           ^
--- a/nashorn/test/script/basic/es6/let-load.js	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/let-load.js	Thu Nov 27 16:42:53 2014 +0100
@@ -40,17 +40,8 @@
 }
 
 print("imported var: " + a);
-try {
-    print("imported let: " + b);
-} catch (e) {
-    print(e);
-}
-
-try {
-    print("imported const: " + c);
-} catch (e) {
-    print(e);
-}
+print("imported let: " + b);
+print("imported const: " + c);
 
 top();
 
@@ -60,4 +51,10 @@
     print(e);
 }
 
+try {
+    c = "foo";
+} catch (e) {
+    print(e);
+}
 
+
--- a/nashorn/test/script/basic/es6/let-load.js.EXPECTED	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/let-load.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -6,3 +6,4 @@
 imported const: 3
 top level function
 ReferenceError: "block" is not defined
+TypeError: Assignment to constant "c"
--- a/nashorn/test/script/basic/es6/let_const_closure.js.EXPECTED	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/let_const_closure.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -5,9 +5,9 @@
 test
 test
 test
-3
-3
-3
 0
 1
 2
+0
+1
+2
--- a/nashorn/test/script/basic/es6/lexical-toplevel.js.EXPECTED	Thu Nov 27 17:14:01 2014 +0400
+++ b/nashorn/test/script/basic/es6/lexical-toplevel.js.EXPECTED	Thu Nov 27 16:42:53 2014 +0100
@@ -13,6 +13,7 @@
 false
 true
 true
+TypeError: Assignment to constant "CONST"
 VAR
 LETLET
 CONST
@@ -28,3 +29,4 @@
 false
 true
 true
+TypeError: Assignment to constant "CONST"