8193371: Use Dynalink REMOVE operation in Nashorn
authorattila
Wed, 20 Dec 2017 17:36:50 +0100
changeset 48411 4ff5c5206427
parent 48410 8aab0cea56bf
child 48412 d4412e380f6b
8193371: Use Dynalink REMOVE operation in Nashorn Reviewed-by: hannesw, sundar
src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java
src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Undefined.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java
src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java
test/nashorn/script/basic/JDK-8193371.js
test/nashorn/script/basic/JDK-8193371.js.EXPECTED
--- a/src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java	Wed Dec 20 17:36:50 2017 +0100
@@ -414,20 +414,21 @@
 
     protected GuardedInvocationComponent getGuardedInvocationComponent(final ComponentLinkRequest req)
     throws Exception {
-        if (!req.namespaces.isEmpty()) {
-            final Namespace ns = req.namespaces.get(0);
-            final Operation op = req.baseOperation;
-            if (op == StandardOperation.GET) {
-                if (ns == StandardNamespace.PROPERTY) {
-                    return getPropertyGetter(req.popNamespace());
-                } else if (ns == StandardNamespace.METHOD) {
-                    return getMethodGetter(req.popNamespace());
-                }
-            } else if (op == StandardOperation.SET && ns == StandardNamespace.PROPERTY) {
-                return getPropertySetter(req.popNamespace());
+        if (req.namespaces.isEmpty()) {
+            return null;
+        }
+        final Namespace ns = req.namespaces.get(0);
+        final Operation op = req.baseOperation;
+        if (op == StandardOperation.GET) {
+            if (ns == StandardNamespace.PROPERTY) {
+                return getPropertyGetter(req.popNamespace());
+            } else if (ns == StandardNamespace.METHOD) {
+                return getMethodGetter(req.popNamespace());
             }
+        } else if (op == StandardOperation.SET && ns == StandardNamespace.PROPERTY) {
+            return getPropertySetter(req.popNamespace());
         }
-        return null;
+        return getNextComponent(req.popNamespace());
     }
 
     GuardedInvocationComponent getNextComponent(final ComponentLinkRequest req) throws Exception {
--- a/src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java	Wed Dec 20 17:36:50 2017 +0100
@@ -136,24 +136,21 @@
 
     @Override
     protected GuardedInvocationComponent getGuardedInvocationComponent(final ComponentLinkRequest req) throws Exception {
-        final GuardedInvocationComponent superGic = super.getGuardedInvocationComponent(req);
-        if(superGic != null) {
-            return superGic;
+        if (req.namespaces.isEmpty()) {
+            return null;
         }
-        if (!req.namespaces.isEmpty()) {
+        final Namespace ns = req.namespaces.get(0);
+        if (ns == StandardNamespace.ELEMENT) {
             final Operation op = req.baseOperation;
-            final Namespace ns = req.namespaces.get(0);
-            if (ns == StandardNamespace.ELEMENT) {
-                if (op == StandardOperation.GET) {
-                    return getElementGetter(req.popNamespace());
-                } else if (op == StandardOperation.SET) {
-                    return getElementSetter(req.popNamespace());
-                } else if (op == StandardOperation.REMOVE) {
-                    return getElementRemover(req.popNamespace());
-                }
+            if (op == StandardOperation.GET) {
+                return getElementGetter(req.popNamespace());
+            } else if (op == StandardOperation.SET) {
+                return getElementSetter(req.popNamespace());
+            } else if (op == StandardOperation.REMOVE) {
+                return getElementRemover(req.popNamespace());
             }
         }
-        return null;
+        return super.getGuardedInvocationComponent(req);
     }
 
     @Override
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java	Wed Dec 20 17:36:50 2017 +0100
@@ -59,6 +59,7 @@
 import java.util.Map;
 import java.util.Set;
 import jdk.nashorn.internal.ir.AccessNode;
+import jdk.nashorn.internal.ir.BaseNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.CatchNode;
@@ -735,72 +736,13 @@
 
     @Override
     public Node leaveUnaryNode(final UnaryNode unaryNode) {
-        switch (unaryNode.tokenType()) {
-        case DELETE:
-            return leaveDELETE(unaryNode);
-        case TYPEOF:
+        if (unaryNode.tokenType() == TokenType.TYPEOF) {
             return leaveTYPEOF(unaryNode);
-        default:
+        } else {
             return super.leaveUnaryNode(unaryNode);
         }
     }
 
-    private Node leaveDELETE(final UnaryNode unaryNode) {
-        final FunctionNode currentFunctionNode = lc.getCurrentFunction();
-        final boolean      strictMode          = currentFunctionNode.isStrict();
-        final Expression   rhs                 = unaryNode.getExpression();
-        final Expression   strictFlagNode      = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this);
-
-        Request request = Request.DELETE;
-        final List<Expression> args = new ArrayList<>();
-
-        if (rhs instanceof IdentNode) {
-            final IdentNode ident = (IdentNode)rhs;
-            // If this is a declared variable or a function parameter, delete always fails (except for globals).
-            final String name = ident.getName();
-            final Symbol symbol = ident.getSymbol();
-
-            if (symbol.isThis()) {
-                // Can't delete "this", ignore and return true
-                return LiteralNode.newInstance(unaryNode, true);
-            }
-            final Expression literalNode = LiteralNode.newInstance(unaryNode, name);
-            final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel())));
-
-            if (!failDelete) {
-                args.add(compilerConstantIdentifier(SCOPE));
-            }
-            args.add(literalNode);
-            args.add(strictFlagNode);
-
-            if (failDelete) {
-                request = Request.FAIL_DELETE;
-            } else if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) {
-                request = Request.SLOW_DELETE;
-            }
-        } else if (rhs instanceof AccessNode) {
-            final Expression base     = ((AccessNode)rhs).getBase();
-            final String     property = ((AccessNode)rhs).getProperty();
-
-            args.add(base);
-            args.add(LiteralNode.newInstance(unaryNode, property));
-            args.add(strictFlagNode);
-
-        } else if (rhs instanceof IndexNode) {
-            final IndexNode indexNode = (IndexNode)rhs;
-            final Expression base  = indexNode.getBase();
-            final Expression index = indexNode.getIndex();
-
-            args.add(base);
-            args.add(index);
-            args.add(strictFlagNode);
-
-        } else {
-            throw new AssertionError("Unexpected delete with " + rhs.getClass().getName() + " expression");
-        }
-        return new RuntimeNode(unaryNode, request, args);
-    }
-
     @Override
     public Node leaveForNode(final ForNode forNode) {
         if (forNode.isForInOrOf()) {
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java	Wed Dec 20 17:36:50 2017 +0100
@@ -151,6 +151,7 @@
 import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
 import jdk.nashorn.internal.runtime.arrays.ArrayData;
 import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
+import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
 import jdk.nashorn.internal.runtime.logging.DebugLogger;
 import jdk.nashorn.internal.runtime.logging.Loggable;
 import jdk.nashorn.internal.runtime.logging.Logger;
@@ -1142,6 +1143,12 @@
             }
 
             @Override
+            public boolean enterDELETE(final UnaryNode unaryNode) {
+                loadDELETE(unaryNode);
+                return false;
+            }
+
+            @Override
             public boolean enterEQ(final BinaryNode binaryNode) {
                 loadCmp(binaryNode, Condition.EQ);
                 return false;
@@ -3791,6 +3798,53 @@
         }
     }
 
+    public void loadDELETE(final UnaryNode unaryNode) {
+        final Expression expression = unaryNode.getExpression();
+        if (expression instanceof IdentNode) {
+            final IdentNode ident = (IdentNode)expression;
+            final Symbol symbol = ident.getSymbol();
+            final String name = ident.getName();
+
+            if (symbol.isThis()) {
+                // Can't delete "this", ignore and return true
+                if (!lc.popDiscardIfCurrent(unaryNode)) {
+                    method.load(true);
+                }
+            } else if (lc.getCurrentFunction().isStrict()) {
+                // All other scope identifier delete attempts fail for strict mode
+                method.load(name);
+                method.invoke(ScriptRuntime.STRICT_FAIL_DELETE);
+            } else if (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))) {
+                // If symbol is a function parameter, or a declared non-global variable, delete is a no-op and returns false.
+                if (!lc.popDiscardIfCurrent(unaryNode)) {
+                    method.load(false);
+                }
+            } else {
+                method.loadCompilerConstant(SCOPE);
+                method.load(name);
+                if ((symbol.isGlobal() && !symbol.isFunctionDeclaration()) || symbol.isProgramLevel()) {
+                    method.invoke(ScriptRuntime.SLOW_DELETE);
+                } else {
+                    method.load(false); // never strict here; that was handled with STRICT_FAIL_DELETE above.
+                    method.invoke(ScriptObject.DELETE);
+                }
+            }
+        } else if (expression instanceof BaseNode) {
+            loadExpressionAsObject(((BaseNode)expression).getBase());
+            if (expression instanceof AccessNode) {
+                final AccessNode accessNode = (AccessNode) expression;
+                method.dynamicRemove(accessNode.getProperty(), getCallSiteFlags(), accessNode.isIndex());
+            } else if (expression instanceof IndexNode) {
+                loadExpressionAsObject(((IndexNode) expression).getIndex());
+                method.dynamicRemoveIndex(getCallSiteFlags());
+            } else {
+                throw new AssertionError(expression.getClass().getName());
+            }
+        } else {
+            throw new AssertionError(expression.getClass().getName());
+        }
+    }
+
     public void loadADD(final BinaryNode binaryNode, final TypeBounds resultBounds) {
         new OptimisticOperation(binaryNode, resultBounds) {
             @Override
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java	Wed Dec 20 17:36:50 2017 +0100
@@ -43,6 +43,7 @@
 import java.util.Set;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
+import jdk.nashorn.internal.ir.BaseNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.BreakNode;
@@ -1093,9 +1094,15 @@
     @Override
     public boolean enterUnaryNode(final UnaryNode unaryNode) {
         final Expression expr = unaryNode.getExpression();
-        final LvarType unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType());
-        if(unaryNode.isSelfModifying() && expr instanceof IdentNode) {
-            onSelfAssignment((IdentNode)expr, unaryType);
+        final LvarType unaryType;
+        if (unaryNode.tokenType() == TokenType.DELETE && expr instanceof IdentNode) {
+            // not visiting deleted identifiers; they don't count as use
+            unaryType = toLvarType(unaryNode.getType());
+        } else {
+            unaryType = toLvarType(unaryNode.setExpression(visitExpression(expr).typeExpression).getType());
+            if (unaryNode.isSelfModifying() && expr instanceof IdentNode) {
+                onSelfAssignment((IdentNode) expr, unaryType);
+            }
         }
         typeStack.push(unaryType);
         return false;
@@ -1348,6 +1355,12 @@
                 return true;
             }
 
+            @Override
+            public boolean enterUnaryNode(final UnaryNode unaryNode) {
+                // not visiting deleted identifiers; they don't count as use
+                return !(unaryNode.tokenType() == TokenType.DELETE && unaryNode.getExpression() instanceof IdentNode);
+            }
+
             @SuppressWarnings("fallthrough")
             @Override
             public Node leaveBinaryNode(final BinaryNode binaryNode) {
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java	Wed Dec 20 17:36:50 2017 +0100
@@ -2203,7 +2203,7 @@
     }
 
     /**
-     * Generate dynamic getter. Pop scope from stack. Push result
+     * Generate dynamic getter. Pop object from stack. Push result.
      *
      * @param valueType type of the value to set
      * @param name      name of property
@@ -2224,7 +2224,7 @@
             type = Type.OBJECT; //promote e.g strings to object generic setter
         }
 
-        popType(Type.SCOPE);
+        popType(Type.OBJECT);
         method.visitInvokeDynamicInsn(NameCodec.encode(name),
                 Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags | dynGetOperation(isMethod, isIndex));
 
@@ -2256,13 +2256,38 @@
             convert(Type.OBJECT); //TODO bad- until we specialize boolean setters,
         }
         popType(type);
-        popType(Type.SCOPE);
+        popType(Type.OBJECT);
 
         method.visitInvokeDynamicInsn(NameCodec.encode(name),
                 methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags | dynSetOperation(isIndex));
     }
 
-     /**
+    /**
+     * Generate dynamic remover. Pop object from stack. Push result.
+     *
+     * @param name      name of property
+     * @param flags     call site flags
+     * @return the method emitter
+     */
+    MethodEmitter dynamicRemove(final String name, final int flags, final boolean isIndex) {
+        if (name.length() > LARGE_STRING_THRESHOLD) { // use removeIndex for extremely long names
+            return load(name).dynamicRemoveIndex(flags);
+        }
+
+        debug("dynamic_remove", name, Type.BOOLEAN, getProgramPoint(flags));
+
+        popType(Type.OBJECT);
+        // Type is widened to OBJECT then coerced back to BOOLEAN
+        method.visitInvokeDynamicInsn(NameCodec.encode(name),
+                Type.getMethodDescriptor(Type.OBJECT, Type.OBJECT), LINKERBOOTSTRAP, flags | dynRemoveOperation(isIndex));
+
+        pushType(Type.OBJECT);
+        convert(Type.BOOLEAN); //most probably a nop
+
+        return this;
+    }
+
+    /**
      * Dynamic getter for indexed structures. Pop index and receiver from stack,
      * generate appropriate signatures based on types
      *
@@ -2342,6 +2367,35 @@
     }
 
     /**
+     * Dynamic remover for indexed structures. Pop index and receiver from stack,
+     * generate appropriate signatures based on types
+     *
+     * @param flags call site flags for getter
+     *
+     * @return the method emitter
+     */
+    MethodEmitter dynamicRemoveIndex(final int flags) {
+        debug("dynamic_remove_index", peekType(1), "[", peekType(), "]", getProgramPoint(flags));
+
+        Type index = peekType();
+        if (index.isObject() || index.isBoolean()) {
+            index = Type.OBJECT; //e.g. string->object
+            convert(Type.OBJECT);
+        }
+        popType();
+
+        popType(Type.OBJECT);
+
+        final String signature = Type.getMethodDescriptor(Type.OBJECT, Type.OBJECT /*e.g STRING->OBJECT*/, index);
+
+        method.visitInvokeDynamicInsn(EMPTY_NAME, signature, LINKERBOOTSTRAP, flags | dynRemoveOperation(true));
+        pushType(Type.OBJECT);
+        convert(Type.BOOLEAN);
+
+        return this;
+    }
+
+    /**
      * Load a key value in the proper form.
      *
      * @param key
@@ -2520,6 +2574,10 @@
         return isIndex ? NashornCallSiteDescriptor.SET_ELEMENT : NashornCallSiteDescriptor.SET_PROPERTY;
     }
 
+    private static int dynRemoveOperation(final boolean isIndex) {
+        return isIndex ? NashornCallSiteDescriptor.REMOVE_ELEMENT : NashornCallSiteDescriptor.REMOVE_PROPERTY;
+    }
+
     private Type emitLocalVariableConversion(final LocalVariableConversion conversion, final boolean onlySymbolLiveValue) {
         final Type from = conversion.getFrom();
         final Type to = conversion.getTo();
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java	Wed Dec 20 17:36:50 2017 +0100
@@ -54,12 +54,6 @@
         TYPEOF,
         /** Reference error type */
         REFERENCE_ERROR,
-        /** Delete operator */
-        DELETE(TokenType.DELETE, Type.BOOLEAN, 1),
-        /** Delete operator for slow scopes */
-        SLOW_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false),
-        /** Delete operator that always fails -- see Lower */
-        FAIL_DELETE(TokenType.DELETE, Type.BOOLEAN, 1, false),
         /** === operator with at least one object */
         EQ_STRICT(TokenType.EQ_STRICT, Type.BOOLEAN, 2, true),
         /** == operator with at least one object */
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java	Wed Dec 20 17:36:50 2017 +0100
@@ -189,6 +189,8 @@
     /** Method handle for generic property setter */
     public static final Call GENERIC_SET = virtualCallNoLookup(ScriptObject.class, "set", void.class, Object.class, Object.class, int.class);
 
+    public static final Call DELETE = virtualCall(MethodHandles.lookup(), ScriptObject.class, "delete", boolean.class, Object.class, boolean.class);
+
     static final MethodHandle[] SET_SLOW = new MethodHandle[] {
         findOwnMH_V("set", void.class, Object.class, int.class, int.class),
         findOwnMH_V("set", void.class, Object.class, double.class, int.class),
@@ -202,6 +204,9 @@
     static final MethodHandle EXTENSION_CHECK   = findOwnMH_V("extensionCheck", boolean.class, boolean.class, String.class);
     static final MethodHandle ENSURE_SPILL_SIZE = findOwnMH_V("ensureSpillSize", Object.class, int.class);
 
+    private static final GuardedInvocation DELETE_GUARDED = new GuardedInvocation(MH.insertArguments(DELETE.methodHandle(), 2, false), NashornGuards.getScriptObjectGuard());
+    private static final GuardedInvocation DELETE_GUARDED_STRICT = new GuardedInvocation(MH.insertArguments(DELETE.methodHandle(), 2, true), NashornGuards.getScriptObjectGuard());
+
     /**
      * Constructor
      */
@@ -1869,6 +1874,13 @@
             return desc.getOperation() instanceof NamedOperation
                     ? findSetMethod(desc, request)
                     : findSetIndexMethod(desc, request);
+        case REMOVE:
+            final GuardedInvocation inv = NashornCallSiteDescriptor.isStrict(desc) ? DELETE_GUARDED_STRICT : DELETE_GUARDED;
+            final Object name = NamedOperation.getName(desc.getOperation());
+            if (name != null) {
+                return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard());
+            }
+            return inv;
         case CALL:
             return findCallMethod(desc, request);
         case NEW:
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java	Wed Dec 20 17:36:50 2017 +0100
@@ -136,6 +136,16 @@
     public static final Call INVALIDATE_RESERVED_BUILTIN_NAME = staticCallNoLookup(ScriptRuntime.class, "invalidateReservedBuiltinName", void.class, String.class);
 
     /**
+     * Used to perform failed delete under strict mode
+     */
+    public static final Call STRICT_FAIL_DELETE = staticCallNoLookup(ScriptRuntime.class, "strictFailDelete", boolean.class, String.class);
+
+    /**
+     * Used to find the scope for slow delete
+     */
+    public static final Call SLOW_DELETE = staticCallNoLookup(ScriptRuntime.class, "slowDelete", boolean.class, ScriptObject.class, String.class);
+
+    /**
      * Converts a switch tag value to a simple integer. deflt value if it can't.
      *
      * @param tag   Switch statement tag value.
@@ -780,87 +790,40 @@
     }
 
     /**
-     * ECMA 11.4.1 - delete operation, generic implementation
+     * ECMA 11.4.1 - delete operator, implementation for slow scopes
      *
-     * @param obj       object with property to delete
+     * This implementation of 'delete' walks the scope chain to find the scope that contains the
+     * property to be deleted, then invokes delete on it. Always used on scopes, never strict.
+     *
+     * @param obj       top scope object
      * @param property  property to delete
-     * @param strict    are we in strict mode
      *
      * @return true if property was successfully found and deleted
      */
-    public static boolean DELETE(final Object obj, final Object property, final Object strict) {
-        if (obj instanceof ScriptObject) {
-            return ((ScriptObject)obj).delete(property, Boolean.TRUE.equals(strict));
-        }
-
-        if (obj instanceof Undefined) {
-            return ((Undefined)obj).delete(property, false);
-        }
-
-        if (obj == null) {
-            throw typeError("cant.delete.property", safeToString(property), "null");
-        }
-
-        if (obj instanceof ScriptObjectMirror) {
-            return ((ScriptObjectMirror)obj).delete(property);
-        }
-
-        if (JSType.isPrimitive(obj)) {
-            return ((ScriptObject) JSType.toScriptObject(obj)).delete(property, Boolean.TRUE.equals(strict));
-        }
-
-        if (obj instanceof JSObject) {
-            ((JSObject)obj).removeMember(Objects.toString(property));
-            return true;
+    public static boolean slowDelete(final ScriptObject obj, final String property) {
+        ScriptObject sobj = obj;
+        while (sobj != null && sobj.isScope()) {
+            final FindProperty find = sobj.findProperty(property, false);
+            if (find != null) {
+                return sobj.delete(property, false);
+            }
+            sobj = sobj.getProto();
         }
-
-        // if object is not reference type, vacuously delete is successful.
-        return true;
-    }
-
-    /**
-     * ECMA 11.4.1 - delete operator, implementation for slow scopes
-     *
-     * This implementation of 'delete' walks the scope chain to find the scope that contains the
-     * property to be deleted, then invokes delete on it.
-     *
-     * @param obj       top scope object
-     * @param property  property to delete
-     * @param strict    are we in strict mode
-     *
-     * @return true if property was successfully found and deleted
-     */
-    public static boolean SLOW_DELETE(final Object obj, final Object property, final Object strict) {
-        if (obj instanceof ScriptObject) {
-            ScriptObject sobj = (ScriptObject) obj;
-            final String key = property.toString();
-            while (sobj != null && sobj.isScope()) {
-                final FindProperty find = sobj.findProperty(key, false);
-                if (find != null) {
-                    return sobj.delete(key, Boolean.TRUE.equals(strict));
-                }
-                sobj = sobj.getProto();
-            }
-        }
-        return DELETE(obj, property, strict);
+        return obj.delete(property, false);
     }
 
     /**
      * ECMA 11.4.1 - delete operator, special case
      *
-     * This is 'delete' that always fails. We have to check strict mode and throw error.
-     * That is why this is a runtime function. Or else we could have inlined 'false'.
+     * This is 'delete' on a scope; it always fails under strict mode.
+     * It always throws an exception, but is declared to return a boolean
+     * to be compatible with the delete operator type.
      *
      * @param property  property to delete
-     * @param strict    are we in strict mode
-     *
-     * @return false always
+     * @return nothing, always throws an exception.
      */
-    public static boolean FAIL_DELETE(final Object property, final Object strict) {
-        if (Boolean.TRUE.equals(strict)) {
-            throw syntaxError("strict.cant.delete", safeToString(property));
-        }
-        return false;
+    public static boolean strictFailDelete(final String property) {
+        throw syntaxError("strict.cant.delete", property);
     }
 
     /**
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Undefined.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Undefined.java	Wed Dec 20 17:36:50 2017 +0100
@@ -112,6 +112,11 @@
                 return findSetIndexMethod(desc);
             }
             return findSetMethod(desc);
+        case REMOVE:
+            if (!(desc.getOperation() instanceof NamedOperation)) {
+                return findDeleteIndexMethod(desc);
+            }
+            return findDeleteMethod(desc);
         default:
         }
         return null;
@@ -124,6 +129,7 @@
 
     private static final MethodHandle GET_METHOD = findOwnMH("get", Object.class, Object.class);
     private static final MethodHandle SET_METHOD = MH.insertArguments(findOwnMH("set", void.class, Object.class, Object.class, int.class), 3, NashornCallSiteDescriptor.CALLSITE_STRICT);
+    private static final MethodHandle DELETE_METHOD = MH.insertArguments(findOwnMH("delete", boolean.class, Object.class, boolean.class), 2, false);
 
     private static GuardedInvocation findGetMethod(final CallSiteDescriptor desc) {
         return new GuardedInvocation(MH.insertArguments(GET_METHOD, 1, NashornCallSiteDescriptor.getOperand(desc)), UNDEFINED_GUARD).asType(desc);
@@ -141,6 +147,15 @@
         return new GuardedInvocation(SET_METHOD, UNDEFINED_GUARD).asType(desc);
     }
 
+    private static GuardedInvocation findDeleteMethod(final CallSiteDescriptor desc) {
+        return new GuardedInvocation(MH.insertArguments(DELETE_METHOD, 1, NashornCallSiteDescriptor.getOperand(desc)), UNDEFINED_GUARD).asType(desc);
+    }
+
+    private static GuardedInvocation findDeleteIndexMethod(final CallSiteDescriptor desc) {
+        return new GuardedInvocation(DELETE_METHOD, UNDEFINED_GUARD).asType(desc);
+    }
+
+
     @Override
     public Object get(final Object key) {
         throw typeError("cant.read.property.of.undefined", ScriptRuntime.safeToString(key));
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java	Wed Dec 20 17:36:50 2017 +0100
@@ -31,7 +31,7 @@
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
 import java.util.Map;
-import javax.script.Bindings;
+import java.util.Objects;
 import jdk.dynalink.CallSiteDescriptor;
 import jdk.dynalink.Operation;
 import jdk.dynalink.StandardOperation;
@@ -64,11 +64,10 @@
         return canLinkTypeStatic(type);
     }
 
-    static boolean canLinkTypeStatic(final Class<?> type) {
-        // can link JSObject also handles Map, Bindings to make
+    private static boolean canLinkTypeStatic(final Class<?> type) {
+        // can link JSObject also handles Map (this includes Bindings) to make
         // sure those are not JSObjects.
         return Map.class.isAssignableFrom(type) ||
-               Bindings.class.isAssignableFrom(type) ||
                JSObject.class.isAssignableFrom(type);
     }
 
@@ -84,7 +83,7 @@
         if (self instanceof JSObject) {
             inv = lookup(desc, request, linkerServices);
             inv = inv.replaceMethods(linkerServices.filterInternalObjects(inv.getInvocation()), inv.getGuard());
-        } else if (self instanceof Map || self instanceof Bindings) {
+        } else if (self instanceof Map) {
             // guard to make sure the Map or Bindings does not turn into JSObject later!
             final GuardedInvocation beanInv = nashornBeansLinker.getGuardedInvocation(request, linkerServices);
             inv = new GuardedInvocation(beanInv.getInvocation(),
@@ -116,6 +115,13 @@
                     return name != null ? findSetMethod(name) : findSetIndexMethod();
                 }
                 break;
+            case REMOVE:
+                if (NashornCallSiteDescriptor.hasStandardNamespace(desc)) {
+                    return new GuardedInvocation(
+                            name == null ? JSOBJECTLINKER_DEL : MH.insertArguments(JSOBJECTLINKER_DEL, 1, name),
+                            IS_JSOBJECT_GUARD);
+                }
+                break;
             case CALL:
                 return findCallMethod(desc);
             case NEW:
@@ -206,6 +212,15 @@
         }
     }
 
+    @SuppressWarnings("unused")
+    private static boolean del(final Object jsobj, final Object key) {
+        if (jsobj instanceof ScriptObjectMirror) {
+            return ((ScriptObjectMirror)jsobj).delete(key);
+        }
+        ((JSObject) jsobj).removeMember(Objects.toString(key));
+        return true;
+    }
+
     private static int getIndex(final Number n) {
         final double value = n.doubleValue();
         return JSType.isRepresentableAsInt(value) ? (int)value : -1;
@@ -245,6 +260,7 @@
     private static final MethodHandle IS_JSOBJECT_GUARD  = findOwnMH_S("isJSObject", boolean.class, Object.class);
     private static final MethodHandle JSOBJECTLINKER_GET = findOwnMH_S("get", Object.class, MethodHandle.class, Object.class, Object.class);
     private static final MethodHandle JSOBJECTLINKER_PUT = findOwnMH_S("put", Void.TYPE, Object.class, Object.class, Object.class);
+    private static final MethodHandle JSOBJECTLINKER_DEL = findOwnMH_S("del", boolean.class, Object.class, Object.class);
 
     // method handles of JSObject class
     private static final MethodHandle JSOBJECT_GETMEMBER     = findJSObjectMH_V("getMember", Object.class, String.class);
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java	Wed Dec 20 17:36:50 2017 +0100
@@ -39,11 +39,13 @@
 import jdk.dynalink.NamedOperation;
 import jdk.dynalink.Operation;
 import jdk.dynalink.beans.BeansLinker;
+import jdk.dynalink.beans.StaticClass;
 import jdk.dynalink.linker.GuardedInvocation;
 import jdk.dynalink.linker.GuardingDynamicLinker;
 import jdk.dynalink.linker.GuardingTypeConverterFactory;
 import jdk.dynalink.linker.LinkRequest;
 import jdk.dynalink.linker.LinkerServices;
+import jdk.dynalink.linker.support.Guards;
 import jdk.dynalink.linker.support.Lookup;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.ECMAException;
@@ -86,12 +88,16 @@
             MH.dropArguments(EMPTY_PROP_SETTER, 0, Object.class);
 
     private static final MethodHandle THROW_STRICT_PROPERTY_SETTER;
+    private static final MethodHandle THROW_STRICT_PROPERTY_REMOVER;
     private static final MethodHandle THROW_OPTIMISTIC_UNDEFINED;
+    private static final MethodHandle MISSING_PROPERTY_REMOVER;
 
     static {
         final Lookup lookup = new Lookup(MethodHandles.lookup());
         THROW_STRICT_PROPERTY_SETTER = lookup.findOwnStatic("throwStrictPropertySetter", void.class, Object.class, Object.class);
+        THROW_STRICT_PROPERTY_REMOVER = lookup.findOwnStatic("throwStrictPropertyRemover", boolean.class, Object.class, Object.class);
         THROW_OPTIMISTIC_UNDEFINED = lookup.findOwnStatic("throwOptimisticUndefined", Object.class, int.class);
+        MISSING_PROPERTY_REMOVER = lookup.findOwnStatic("missingPropertyRemover", boolean.class, Object.class, Object.class);
     }
 
     private static GuardedInvocation linkBean(final LinkRequest linkRequest) throws Exception {
@@ -124,6 +130,7 @@
     static MethodHandle linkMissingBeanMember(final LinkRequest linkRequest, final LinkerServices linkerServices) throws Exception {
         final CallSiteDescriptor desc = linkRequest.getCallSiteDescriptor();
         final String operand = NashornCallSiteDescriptor.getOperand(desc);
+        final boolean strict = NashornCallSiteDescriptor.isStrict(desc);
         switch (NashornCallSiteDescriptor.getStandardOperation(desc)) {
         case GET:
             if (NashornCallSiteDescriptor.isOptimistic(desc)) {
@@ -133,13 +140,17 @@
             }
             return getInvocation(EMPTY_ELEM_GETTER, linkerServices, desc);
         case SET:
-            final boolean strict = NashornCallSiteDescriptor.isStrict(desc);
             if (strict) {
                 return adaptThrower(bindOperand(THROW_STRICT_PROPERTY_SETTER, operand), desc);
             } else if (operand != null) {
                 return getInvocation(EMPTY_PROP_SETTER, linkerServices, desc);
             }
             return getInvocation(EMPTY_ELEM_SETTER, linkerServices, desc);
+        case REMOVE:
+            if (strict) {
+                return adaptThrower(bindOperand(THROW_STRICT_PROPERTY_REMOVER, operand), desc);
+            }
+            return getInvocation(bindOperand(MISSING_PROPERTY_REMOVER, operand), linkerServices, desc);
         default:
             throw new AssertionError("unknown call type " + desc);
         }
@@ -162,6 +173,33 @@
         throw createTypeError(self, name, "cant.set.property");
     }
 
+    @SuppressWarnings("unused")
+    private static boolean throwStrictPropertyRemover(final Object self, final Object name) {
+        if (isNonConfigurableProperty(self, name)) {
+            throw createTypeError(self, name, "cant.delete.property");
+        }
+        return true;
+    }
+
+    @SuppressWarnings("unused")
+    private static boolean missingPropertyRemover(final Object self, final Object name) {
+        return !isNonConfigurableProperty(self, name);
+    }
+
+    // Corresponds to ECMAScript 5.1 8.12.7 [[Delete]] point 3 check for "isConfigurable" (but negated)
+    private static boolean isNonConfigurableProperty(final Object self, final Object name) {
+        if (self instanceof StaticClass) {
+            final Class<?> clazz = ((StaticClass)self).getRepresentedClass();
+            return BeansLinker.getReadableStaticPropertyNames(clazz).contains(name) ||
+                   BeansLinker.getWritableStaticPropertyNames(clazz).contains(name) ||
+                   BeansLinker.getStaticMethodNames(clazz).contains(name);
+        }
+        final Class<?> clazz = self.getClass();
+        return BeansLinker.getReadableInstancePropertyNames(clazz).contains(name) ||
+            BeansLinker.getWritableInstancePropertyNames(clazz).contains(name) ||
+            BeansLinker.getInstanceMethodNames(clazz).contains(name);
+    }
+
     private static ECMAException createTypeError(final Object self, final Object name, final String msg) {
         return typeError(msg, String.valueOf(name), ScriptRuntime.safeToString(self));
     }
@@ -215,6 +253,8 @@
             throw typeError(NashornCallSiteDescriptor.isMethodFirstOperation(desc) ? "no.such.function" : "cant.get.property", getArgument(linkRequest), "null");
         case SET:
             throw typeError("cant.set.property", getArgument(linkRequest), "null");
+        case REMOVE:
+            throw typeError("cant.delete.property", getArgument(linkRequest), "null");
         default:
             throw new AssertionError("unknown call type " + desc);
         }
--- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java	Wed Dec 20 11:40:45 2017 -0800
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java	Wed Dec 20 17:36:50 2017 +0100
@@ -29,6 +29,7 @@
 import static jdk.dynalink.StandardNamespace.METHOD;
 import static jdk.dynalink.StandardNamespace.PROPERTY;
 import static jdk.dynalink.StandardOperation.GET;
+import static jdk.dynalink.StandardOperation.REMOVE;
 import static jdk.dynalink.StandardOperation.SET;
 
 import java.lang.invoke.MethodHandles;
@@ -63,7 +64,7 @@
  * form of static methods.
  */
 public final class NashornCallSiteDescriptor extends CallSiteDescriptor {
-    // Lowest three bits describe the operation
+    // Lowest four bits describe the operation
     /** Property getter operation {@code obj.prop} */
     public static final int GET_PROPERTY        = 0;
     /** Element getter operation {@code obj[index]} */
@@ -76,12 +77,16 @@
     public static final int SET_PROPERTY        = 4;
     /** Element setter operation {@code obj[index] = value} */
     public static final int SET_ELEMENT         = 5;
+    /** Property remove operation {@code delete obj.prop} */
+    public static final int REMOVE_PROPERTY     = 6;
+    /** Element remove operation {@code delete obj[index]} */
+    public static final int REMOVE_ELEMENT      = 7;
     /** Call operation {@code fn(args...)} */
-    public static final int CALL                = 6;
+    public static final int CALL                = 8;
     /** New operation {@code new Constructor(args...)} */
-    public static final int NEW                 = 7;
+    public static final int NEW                 = 9;
 
-    private static final int OPERATION_MASK = 7;
+    private static final int OPERATION_MASK = 15;
 
     // Correspond to the operation indices above.
     private static final Operation[] OPERATIONS = new Operation[] {
@@ -91,42 +96,44 @@
         GET.withNamespaces(METHOD, ELEMENT, PROPERTY),
         SET.withNamespaces(PROPERTY, ELEMENT),
         SET.withNamespaces(ELEMENT, PROPERTY),
+        REMOVE.withNamespaces(PROPERTY, ELEMENT),
+        REMOVE.withNamespaces(ELEMENT, PROPERTY),
         StandardOperation.CALL,
         StandardOperation.NEW
     };
 
     /** Flags that the call site references a scope variable (it's an identifier reference or a var declaration, not a
      * property access expression. */
-    public static final int CALLSITE_SCOPE         = 1 << 3;
+    public static final int CALLSITE_SCOPE         = 1 << 4;
     /** Flags that the call site is in code that uses ECMAScript strict mode. */
-    public static final int CALLSITE_STRICT        = 1 << 4;
+    public static final int CALLSITE_STRICT        = 1 << 5;
     /** Flags that a property getter or setter call site references a scope variable that is located at a known distance
      * in the scope chain. Such getters and setters can often be linked more optimally using these assumptions. */
-    public static final int CALLSITE_FAST_SCOPE    = 1 << 5;
+    public static final int CALLSITE_FAST_SCOPE    = 1 << 6;
     /** Flags that a callsite type is optimistic, i.e. we might get back a wider return value than encoded in the
      * descriptor, and in that case we have to throw an UnwarrantedOptimismException */
-    public static final int CALLSITE_OPTIMISTIC    = 1 << 6;
+    public static final int CALLSITE_OPTIMISTIC    = 1 << 7;
     /** Is this really an apply that we try to call as a call? */
-    public static final int CALLSITE_APPLY_TO_CALL = 1 << 7;
+    public static final int CALLSITE_APPLY_TO_CALL = 1 << 8;
     /** Does this a callsite for a variable declaration? */
-    public static final int CALLSITE_DECLARE       = 1 << 8;
+    public static final int CALLSITE_DECLARE       = 1 << 9;
 
     /** Flags that the call site is profiled; Contexts that have {@code "profile.callsites"} boolean property set emit
      * code where call sites have this flag set. */
-    public static final int CALLSITE_PROFILE         = 1 << 9;
+    public static final int CALLSITE_PROFILE         = 1 << 10;
     /** Flags that the call site is traced; Contexts that have {@code "trace.callsites"} property set emit code where
      * call sites have this flag set. */
-    public static final int CALLSITE_TRACE           = 1 << 10;
+    public static final int CALLSITE_TRACE           = 1 << 11;
     /** Flags that the call site linkage miss (and thus, relinking) is traced; Contexts that have the keyword
      * {@code "miss"} in their {@code "trace.callsites"} property emit code where call sites have this flag set. */
-    public static final int CALLSITE_TRACE_MISSES    = 1 << 11;
+    public static final int CALLSITE_TRACE_MISSES    = 1 << 12;
     /** Flags that entry/exit to/from the method linked at call site are traced; Contexts that have the keyword
      * {@code "enterexit"} in their {@code "trace.callsites"} property emit code where call sites have this flag set. */
-    public static final int CALLSITE_TRACE_ENTEREXIT = 1 << 12;
+    public static final int CALLSITE_TRACE_ENTEREXIT = 1 << 13;
     /** Flags that values passed as arguments to and returned from the method linked at call site are traced; Contexts
      * that have the keyword {@code "values"} in their {@code "trace.callsites"} property emit code where call sites
      * have this flag set. */
-    public static final int CALLSITE_TRACE_VALUES    = 1 << 13;
+    public static final int CALLSITE_TRACE_VALUES    = 1 << 14;
 
     //we could have more tracing flags here, for example CALLSITE_TRACE_SCOPE, but bits are a bit precious
     //right now given the program points
@@ -138,10 +145,10 @@
      * TODO: rethink if we need the various profile/trace flags or the linker can use the Context instead to query its
      * trace/profile settings.
      */
-    public static final int CALLSITE_PROGRAM_POINT_SHIFT = 14;
+    public static final int CALLSITE_PROGRAM_POINT_SHIFT = 15;
 
     /**
-     * Maximum program point value. We have 18 bits left over after flags, and
+     * Maximum program point value. We have 17 bits left over after flags, and
      * it should be plenty. Program points are local to a single function. Every
      * function maps to a single JVM bytecode method that can have at most 65535
      * bytes. (Large functions are synthetically split into smaller functions.)
@@ -222,8 +229,10 @@
         case 3: return "GET_METHOD_ELEMENT";
         case 4: return "SET_PROPERTY";
         case 5: return "SET_ELEMENT";
-        case 6: return "CALL";
-        case 7: return "NEW";
+        case 6: return "REMOVE_PROPERTY";
+        case 7: return "REMOVE_ELEMENT";
+        case 8: return "CALL";
+        case 9: return "NEW";
         default: throw new AssertionError();
         }
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nashorn/script/basic/JDK-8193371.js	Wed Dec 20 17:36:50 2017 +0100
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2017 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-8193371: Use Dynalink REMOVE operation in Nashorn
+ *
+ * @test
+ * @run
+ */
+
+// This test exercises new functionality enabled by the issue, namely removal of elements from Java lists and maps.
+
+var ArrayList = java.util.ArrayList;
+var HashMap = java.util.HashMap;
+var listOf = java.util.List.of;
+var mapOf = java.util.Map.of;
+
+// Remove from a list
+(function() { 
+    var a = new ArrayList(listOf("foo", "bar", "baz"));
+    Assert.assertFalse(delete a.add);
+
+    // Delete actual element
+    Assert.assertTrue(delete a[1]);
+    Assert.assertEquals(a, listOf("foo", "baz"));
+
+    // Gracefully ignore silly indices
+    Assert.assertTrue(delete a[5]);
+    Assert.assertTrue(delete a[-1]);
+    Assert.assertTrue(delete a["whatever"]);
+    Assert.assertTrue(delete a.whatever);
+
+    // Gracefully ignore attempts at deleting methods and properties
+    Assert.assertFalse(delete a.add);
+    Assert.assertFalse(delete a.class);
+
+    Assert.assertEquals(a, listOf("foo", "baz"));
+
+    print("List passed.")
+})();
+
+// Remove from a list, strict
+(function() { 
+    "use strict";
+	
+    var a = new ArrayList(listOf("foo", "bar", "baz"));
+
+    // Delete actual element
+    Assert.assertTrue(delete a[1]);
+    Assert.assertEquals(a, listOf("foo", "baz"));
+
+    // Gracefully ignore silly indices
+    Assert.assertTrue(delete a[5]);
+    Assert.assertTrue(delete a[-1]);
+    Assert.assertTrue(delete a["whatever"]);
+    Assert.assertTrue(delete a.whatever);
+
+    // Fail deleting methods and properties
+    try { delete a.add; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    try { delete a.class; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+
+    Assert.assertEquals(a, listOf("foo", "baz"));
+
+    print("Strict list passed.")
+})();
+
+// Remove from a map
+(function() { 
+    var m = new HashMap(mapOf("a", 1, "b", 2, "c", 3));
+
+    // Delete actual elements
+    Assert.assertTrue(delete m.a);
+    Assert.assertEquals(m, mapOf("b", 2, "c", 3));
+    var key = "b"
+    Assert.assertTrue(delete m[key]);
+    Assert.assertEquals(m, mapOf("c", 3));
+
+    // Gracefully ignore silly indices
+    Assert.assertTrue(delete m.x);
+    Assert.assertTrue(delete m[5]);
+    Assert.assertTrue(delete m[-1]);
+    Assert.assertTrue(delete m["whatever"]);
+
+    // Gracefully ignore attempts at deleting methods and properties
+    Assert.assertFalse(delete m.put);
+    Assert.assertFalse(delete m.class);
+
+    Assert.assertEquals(m, mapOf("c", 3));
+    print("Map passed.")
+})();
+
+// Remove from a map, strict
+(function() { 
+    "use strict";
+
+    var m = new HashMap(mapOf("a", 1, "b", 2, "c", 3));
+
+    // Delete actual elements
+    Assert.assertTrue(delete m.a);
+    Assert.assertEquals(m, mapOf("b", 2, "c", 3));
+    var key = "b"
+    Assert.assertTrue(delete m[key]);
+    Assert.assertEquals(m, mapOf("c", 3));
+
+    // Gracefully ignore silly indices
+    Assert.assertTrue(delete m.x);
+    Assert.assertTrue(delete m[5]);
+    Assert.assertTrue(delete m[-1]);
+    Assert.assertTrue(delete m["whatever"]);
+
+    // Fail deleting methods and properties
+    try { delete m.size; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    try { delete m.class; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+
+    // Somewhat counterintuitive, but if we define an element of a map, we can 
+    // delete it, however then the method surfaces, and we can't delete that.
+    m.size = 4
+    Assert.assertTrue(delete m.size)
+    try { delete m.size; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    
+    Assert.assertEquals(m, mapOf("c", 3));
+
+    print("Strict map passed.")
+})();
+
+// Remove from arrays and beans
+(function() { 
+    var a = new (Java.type("int[]"))(2)
+    a[0] = 42
+    a[1] = 13
+    
+    // Huh, Dynalink doesn't expose .clone() on Java arrays?
+    var c = new (Java.type("int[]"))(2)
+    c[0] = 42
+    c[1] = 13
+
+    // passes vacuously, but does nothing
+    Assert.assertTrue(delete a[0])
+    Assert.assertEquals(a, c);
+    
+    var b = new java.util.BitSet()
+    b.set(2)
+    // does nothing
+    Assert.assertFalse(delete b.get)
+    // Method is still there and operational
+    Assert.assertTrue(b.get(2))
+
+    // passes vacuously for non-existant property
+    Assert.assertTrue(delete b.foo)
+
+    // statics
+    var Calendar = java.util.Calendar
+    Assert.assertFalse(delete Calendar.UNDECIMBER) // field
+    Assert.assertFalse(delete Calendar.availableLocales) // property
+    Assert.assertFalse(delete Calendar.getInstance) // method
+    Assert.assertTrue(delete Calendar.BLAH) // no such thing
+  
+    print("Beans passed.")
+})();
+
+// Remove from arrays and beans, strict
+(function() { 
+    "use strict";
+    
+    var a = new (Java.type("int[]"))(2)
+    a[0] = 42
+    a[1] = 13
+
+    var c = new (Java.type("int[]"))(2)
+    c[0] = 42
+    c[1] = 13
+
+    // passes vacuously, but does nothing
+    Assert.assertTrue(delete a[0])
+    Assert.assertEquals(a, c);
+    
+    var b = new java.util.BitSet()
+    b.set(2)
+    // fails to delete a method
+    try { delete b.get; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    // Method is still there and operational
+    Assert.assertTrue(b.get(2))
+
+    // passes vacuously for non-existant property
+    Assert.assertTrue(delete b.foo)
+    
+    // statics
+    var Calendar = java.util.Calendar
+    try { delete Calendar.UNDECIMBER; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    try { delete Calendar.availableLocales; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    try { delete Calendar.getInstance; Assert.fail(); } catch (e) { Assert.assertTrue(e instanceof TypeError) }
+    Assert.assertTrue(delete Calendar.BLAH) // no such thing
+  
+    print("Strict beans passed.")
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/nashorn/script/basic/JDK-8193371.js.EXPECTED	Wed Dec 20 17:36:50 2017 +0100
@@ -0,0 +1,6 @@
+List passed.
+Strict list passed.
+Map passed.
+Strict map passed.
+Beans passed.
+Strict beans passed.