8193371: Use Dynalink REMOVE operation in Nashorn
Reviewed-by: hannesw, sundar
This commit is contained in:
parent
e6680338c5
commit
59c3bea9f1
@ -414,20 +414,21 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
|
||||
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;
|
||||
}
|
||||
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 getNextComponent(req.popNamespace());
|
||||
}
|
||||
|
||||
GuardedInvocationComponent getNextComponent(final ComponentLinkRequest req) throws Exception {
|
||||
|
@ -136,24 +136,21 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
||||
|
||||
@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
|
||||
|
@ -59,6 +59,7 @@ import java.util.ListIterator;
|
||||
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 @@ final class AssignSymbols extends SimpleNodeVisitor implements Loggable {
|
||||
|
||||
@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()) {
|
||||
|
@ -151,6 +151,7 @@ import jdk.nashorn.internal.runtime.Undefined;
|
||||
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;
|
||||
@ -1141,6 +1142,12 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterDELETE(final UnaryNode unaryNode) {
|
||||
loadDELETE(unaryNode);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterEQ(final BinaryNode binaryNode) {
|
||||
loadCmp(binaryNode, Condition.EQ);
|
||||
@ -3791,6 +3798,53 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -43,6 +43,7 @@ import java.util.Map;
|
||||
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 @@ final class LocalVariableTypesCalculator extends SimpleNodeVisitor {
|
||||
@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 @@ final class LocalVariableTypesCalculator extends SimpleNodeVisitor {
|
||||
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) {
|
||||
|
@ -2203,7 +2203,7 @@ public class MethodEmitter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class MethodEmitter {
|
||||
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 @@ public class MethodEmitter {
|
||||
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
|
||||
*
|
||||
@ -2341,6 +2366,35 @@ public class MethodEmitter {
|
||||
LINKERBOOTSTRAP, flags | NashornCallSiteDescriptor.SET_ELEMENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -2520,6 +2574,10 @@ public class MethodEmitter {
|
||||
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();
|
||||
|
@ -54,12 +54,6 @@ public class RuntimeNode extends Expression {
|
||||
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 */
|
||||
|
@ -189,6 +189,8 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
|
||||
/** 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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
|
||||
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 @@ public abstract class ScriptObject implements PropertyAccess, Cloneable {
|
||||
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:
|
||||
|
@ -135,6 +135,16 @@ public final class ScriptRuntime {
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -779,88 +789,41 @@ public final class ScriptRuntime {
|
||||
throw referenceError("cant.be.used.as.lhs", Objects.toString(msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* ECMA 11.4.1 - delete operation, generic implementation
|
||||
*
|
||||
* @param obj object with property to delete
|
||||
* @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;
|
||||
}
|
||||
|
||||
// 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.
|
||||
* 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 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();
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,6 +112,11 @@ public final class Undefined extends DefaultPropertyAccess {
|
||||
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 @@ public final class Undefined extends DefaultPropertyAccess {
|
||||
|
||||
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 @@ public final class Undefined extends DefaultPropertyAccess {
|
||||
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));
|
||||
|
@ -31,7 +31,7 @@ import java.lang.invoke.MethodHandle;
|
||||
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 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
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 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
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 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
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 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
}
|
||||
}
|
||||
|
||||
@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 @@ final class JSObjectLinker implements TypeBasedGuardingDynamicLinker {
|
||||
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);
|
||||
|
@ -39,11 +39,13 @@ import jdk.dynalink.CallSiteDescriptor;
|
||||
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 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
|
||||
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 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
|
||||
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 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
|
||||
}
|
||||
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 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
|
||||
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 @@ final class NashornBottomLinker implements GuardingDynamicLinker, GuardingTypeCo
|
||||
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);
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import static jdk.dynalink.StandardNamespace.ELEMENT;
|
||||
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 @@ import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
* 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 final class NashornCallSiteDescriptor extends CallSiteDescriptor {
|
||||
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 @@ public final class NashornCallSiteDescriptor extends CallSiteDescriptor {
|
||||
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 @@ public final class NashornCallSiteDescriptor extends CallSiteDescriptor {
|
||||
* 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 @@ public final class NashornCallSiteDescriptor extends CallSiteDescriptor {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
215
test/nashorn/script/basic/JDK-8193371.js
Normal file
215
test/nashorn/script/basic/JDK-8193371.js
Normal file
@ -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.")
|
||||
})();
|
6
test/nashorn/script/basic/JDK-8193371.js.EXPECTED
Normal file
6
test/nashorn/script/basic/JDK-8193371.js.EXPECTED
Normal file
@ -0,0 +1,6 @@
|
||||
List passed.
|
||||
Strict list passed.
|
||||
Map passed.
|
||||
Strict map passed.
|
||||
Beans passed.
|
||||
Strict beans passed.
|
Loading…
Reference in New Issue
Block a user