# HG changeset patch # User attila # Date 1513787810 -3600 # Node ID c96d4c720995e1f6dc25a8441954206e710f46ab # Parent 315c690bb90b6f6061c4cdc80038059a79702f8f 8193371: Use Dynalink REMOVE operation in Nashorn Reviewed-by: hannesw, sundar diff -r 315c690bb90b -r c96d4c720995 src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java --- a/src/jdk.dynalink/share/classes/jdk/dynalink/beans/AbstractJavaLinker.java Wed Dec 20 08:05:04 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 { diff -r 315c690bb90b -r c96d4c720995 src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java --- a/src/jdk.dynalink/share/classes/jdk/dynalink/beans/BeanLinker.java Wed Dec 20 08:05:04 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 diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java Wed Dec 20 08:05:04 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 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()) { diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Dec 20 08:05:04 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 diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java Wed Dec 20 08:05:04 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) { diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java Wed Dec 20 08:05:04 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(); diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/ir/RuntimeNode.java Wed Dec 20 08:05:04 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 */ diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptObject.java Wed Dec 20 08:05:04 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: diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/ScriptRuntime.java Wed Dec 20 08:05:04 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); } /** diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Undefined.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/Undefined.java Wed Dec 20 08:05:04 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)); diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/JSObjectLinker.java Wed Dec 20 08:05:04 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); diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornBottomLinker.java Wed Dec 20 08:05:04 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); } diff -r 315c690bb90b -r c96d4c720995 src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java --- a/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/linker/NashornCallSiteDescriptor.java Wed Dec 20 08:05:04 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(); } } diff -r 315c690bb90b -r c96d4c720995 test/nashorn/script/basic/JDK-8193371.js --- /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.") +})(); diff -r 315c690bb90b -r c96d4c720995 test/nashorn/script/basic/JDK-8193371.js.EXPECTED --- /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.