8193371: Use Dynalink REMOVE operation in Nashorn

Reviewed-by: hannesw, sundar
This commit is contained in:
Attila Szegedi 2017-12-20 17:36:50 +01:00
parent e6680338c5
commit 59c3bea9f1
15 changed files with 523 additions and 188 deletions

View File

@ -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 {

View File

@ -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

View File

@ -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()) {

View File

@ -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

View File

@ -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) {

View File

@ -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();

View File

@ -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 */

View File

@ -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:

View File

@ -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);
}
/**

View File

@ -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));

View File

@ -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);

View File

@ -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);
}

View File

@ -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();
}
}

View 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.")
})();

View File

@ -0,0 +1,6 @@
List passed.
Strict list passed.
Map passed.
Strict map passed.
Beans passed.
Strict beans passed.