src/jdk/nashorn/internal/runtime/ScriptObject.java

Print this page
rev 741 : 8029003: setField in ScriptObject is incorrect for non extensible objects
Reviewed-by: lagergren, jlaskey
rev 742 : 8029667: Prototype linking is incorrect
Reviewed-by: jlaskey, sundar
rev 746 : 8031715: Indexed access to java package not working
Reviewed-by: lagergren, hannesw
rev 754 : 8030197: Nashorn: Object.defineProperty() can be lured to change fixed NaN property
Reviewed-by: attila, jlaskey
rev 755 : 8035948: Redesign property listeners for shared classes
Reviewed-by: sundar, lagergren
rev 758 : 8021350: Share script classes between threads/globals within context
Reviewed-by: lagergren, sundar
rev 760 : 8037400: Remove getInitialMap getters and GlobalObject interface
Reviewed-by: lagergren, jlaskey, attila

*** 41,50 **** --- 41,51 ---- import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; + import java.lang.invoke.SwitchPoint; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections;
*** 63,72 **** --- 64,74 ---- import jdk.nashorn.internal.codegen.ObjectClassGenerator; import jdk.nashorn.internal.lookup.Lookup; import jdk.nashorn.internal.lookup.MethodHandleFactory; import jdk.nashorn.internal.objects.AccessorPropertyDescriptor; import jdk.nashorn.internal.objects.DataPropertyDescriptor; + import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.arrays.ArrayIndex; import jdk.nashorn.internal.runtime.linker.Bootstrap; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
*** 86,96 **** * <li>Modifications of the map include adding/deleting attributes or changing a * function field value.</li> * </ul> */ ! public abstract class ScriptObject extends PropertyListenerManager implements PropertyAccess { /** __proto__ special property name */ public static final String PROTO_PROPERTY_NAME = "__proto__"; /** Search fall back routine name for "no such method" */ static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__"; --- 88,98 ---- * <li>Modifications of the map include adding/deleting attributes or changing a * function field value.</li> * </ul> */ ! public abstract class ScriptObject implements PropertyAccess { /** __proto__ special property name */ public static final String PROTO_PROPERTY_NAME = "__proto__"; /** Search fall back routine name for "no such method" */ static final String NO_SUCH_METHOD_NAME = "__noSuchMethod__";
*** 105,117 **** public static final int IS_ARRAY = 0b0000_0010; /** Per ScriptObject flag - is this an arguments object? */ public static final int IS_ARGUMENTS = 0b0000_0100; - /** Is this a prototype PropertyMap? */ - public static final int IS_PROTOTYPE = 0b0000_1000; - /** Is length property not-writable? */ public static final int IS_LENGTH_NOT_WRITABLE = 0b0001_0000; /** Spill growth rate - by how many elements does {@link ScriptObject#spill} when full */ public static final int SPILL_RATE = 8; --- 107,116 ----
*** 131,161 **** /** Indexed array data. */ private ArrayData arrayData; static final MethodHandle GETPROTO = findOwnMH("getProto", ScriptObject.class); static final MethodHandle SETPROTOCHECK = findOwnMH("setProtoCheck", void.class, Object.class); ! static final MethodHandle MEGAMORPHIC_GET = findOwnMH("megamorphicGet", Object.class, String.class, boolean.class); static final MethodHandle SETFIELD = findOwnMH("setField", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, MethodHandle.class, Object.class, Object.class); static final MethodHandle SETSPILL = findOwnMH("setSpill", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); static final MethodHandle SETSPILLWITHNEW = findOwnMH("setSpillWithNew", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); static final MethodHandle SETSPILLWITHGROW = findOwnMH("setSpillWithGrow", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, int.class, Object.class, Object.class); private static final MethodHandle TRUNCATINGFILTER = findOwnMH("truncatingFilter", Object[].class, int.class, Object[].class); private static final MethodHandle KNOWNFUNCPROPGUARD = findOwnMH("knownFunctionPropertyGuard", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, Object.class, ScriptFunction.class); /** Method handle for getting a function argument at a given index. Used from MapCreator */ public static final Call GET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArgument", Object.class, int.class); /** Method handle for setting a function argument at a given index. Used from MapCreator */ public static final Call SET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "setArgument", void.class, int.class, Object.class); /** Method handle for getting the proto of a ScriptObject */ public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class); /** Method handle for setting the proto of a ScriptObject */ ! public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setProto", void.class, ScriptObject.class); /** Method handle for setting the proto of a ScriptObject after checking argument */ public static final Call SET_PROTO_CHECK = virtualCallNoLookup(ScriptObject.class, "setProtoCheck", void.class, Object.class); /** Method handle for setting the user accessors of a ScriptObject */ --- 130,163 ---- /** Indexed array data. */ private ArrayData arrayData; static final MethodHandle GETPROTO = findOwnMH("getProto", ScriptObject.class); static final MethodHandle SETPROTOCHECK = findOwnMH("setProtoCheck", void.class, Object.class); ! static final MethodHandle MEGAMORPHIC_GET = findOwnMH("megamorphicGet", Object.class, String.class, boolean.class, boolean.class); ! static final MethodHandle GLOBALFILTER = findOwnMH("globalFilter", Object.class, Object.class); static final MethodHandle SETFIELD = findOwnMH("setField", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, MethodHandle.class, Object.class, Object.class); static final MethodHandle SETSPILL = findOwnMH("setSpill", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); static final MethodHandle SETSPILLWITHNEW = findOwnMH("setSpillWithNew", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, Object.class, Object.class); static final MethodHandle SETSPILLWITHGROW = findOwnMH("setSpillWithGrow", void.class, CallSiteDescriptor.class, PropertyMap.class, PropertyMap.class, int.class, int.class, Object.class, Object.class); private static final MethodHandle TRUNCATINGFILTER = findOwnMH("truncatingFilter", Object[].class, int.class, Object[].class); private static final MethodHandle KNOWNFUNCPROPGUARD = findOwnMH("knownFunctionPropertyGuard", boolean.class, Object.class, PropertyMap.class, MethodHandle.class, Object.class, ScriptFunction.class); + private static final ArrayList<MethodHandle> protoFilters = new ArrayList<>(); + /** Method handle for getting a function argument at a given index. Used from MapCreator */ public static final Call GET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "getArgument", Object.class, int.class); /** Method handle for setting a function argument at a given index. Used from MapCreator */ public static final Call SET_ARGUMENT = virtualCall(MethodHandles.lookup(), ScriptObject.class, "setArgument", void.class, int.class, Object.class); /** Method handle for getting the proto of a ScriptObject */ public static final Call GET_PROTO = virtualCallNoLookup(ScriptObject.class, "getProto", ScriptObject.class); /** Method handle for setting the proto of a ScriptObject */ ! public static final Call SET_PROTO = virtualCallNoLookup(ScriptObject.class, "setInitialProto", void.class, ScriptObject.class); /** Method handle for setting the proto of a ScriptObject after checking argument */ public static final Call SET_PROTO_CHECK = virtualCallNoLookup(ScriptObject.class, "setProtoCheck", void.class, Object.class); /** Method handle for setting the user accessors of a ScriptObject */
*** 197,210 **** } this.arrayData = ArrayData.EMPTY_ARRAY; this.setMap(map == null ? PropertyMap.newMap() : map); this.proto = proto; - - if (proto != null) { - proto.setIsPrototype(); - } } /** * Copy all properties from the source object with their receiver bound to the source. * This function was known as mergeMap --- 199,208 ----
*** 227,238 **** for (final Property property : properties) { final String key = property.getKey(); final Property oldProp = newMap.findProperty(key); if (oldProp == null) { if (property instanceof UserAccessorProperty) { final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); ! newMap = newMap.addProperty(prop); } else { newMap = newMap.addPropertyBind((AccessorProperty)property, source); } } else { // See ECMA section 10.5 Declaration Binding Instantiation --- 225,237 ---- for (final Property property : properties) { final String key = property.getKey(); final Property oldProp = newMap.findProperty(key); if (oldProp == null) { if (property instanceof UserAccessorProperty) { + // Note: we copy accessor functions to this object which is semantically different from binding. final UserAccessorProperty prop = this.newUserAccessors(key, property.getFlags(), property.getGetterFunction(source), property.getSetterFunction(source)); ! newMap = newMap.addPropertyNoHistory(prop); } else { newMap = newMap.addPropertyBind((AccessorProperty)property, source); } } else { // See ECMA section 10.5 Declaration Binding Instantiation
*** 324,345 **** * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) * * @return property descriptor */ public final PropertyDescriptor toPropertyDescriptor() { ! final GlobalObject global = (GlobalObject) Context.getGlobalTrusted(); final PropertyDescriptor desc; if (isDataDescriptor()) { if (has(SET) || has(GET)) { ! throw typeError((ScriptObject)global, "inconsistent.property.descriptor"); } desc = global.newDataDescriptor(UNDEFINED, false, false, false); } else if (isAccessorDescriptor()) { if (has(VALUE) || has(WRITABLE)) { ! throw typeError((ScriptObject)global, "inconsistent.property.descriptor"); } desc = global.newAccessorDescriptor(UNDEFINED, UNDEFINED, false, false); } else { desc = global.newGenericDescriptor(false, false); --- 323,344 ---- * ECMA 8.10.5 ToPropertyDescriptor ( Obj ) * * @return property descriptor */ public final PropertyDescriptor toPropertyDescriptor() { ! final Global global = Context.getGlobal(); final PropertyDescriptor desc; if (isDataDescriptor()) { if (has(SET) || has(GET)) { ! throw typeError(global, "inconsistent.property.descriptor"); } desc = global.newDataDescriptor(UNDEFINED, false, false, false); } else if (isAccessorDescriptor()) { if (has(VALUE) || has(WRITABLE)) { ! throw typeError(global, "inconsistent.property.descriptor"); } desc = global.newAccessorDescriptor(UNDEFINED, UNDEFINED, false, false); } else { desc = global.newGenericDescriptor(false, false);
*** 354,364 **** * @param global global scope object * @param obj object to create property descriptor from * * @return property descriptor */ ! public static PropertyDescriptor toPropertyDescriptor(final ScriptObject global, final Object obj) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).toPropertyDescriptor(); } throw typeError(global, "not.an.object", ScriptRuntime.safeToString(obj)); --- 353,363 ---- * @param global global scope object * @param obj object to create property descriptor from * * @return property descriptor */ ! public static PropertyDescriptor toPropertyDescriptor(final Global global, final Object obj) { if (obj instanceof ScriptObject) { return ((ScriptObject)obj).toPropertyDescriptor(); } throw typeError(global, "not.an.object", ScriptRuntime.safeToString(obj));
*** 373,383 **** * object, or undefined if absent. */ public Object getOwnPropertyDescriptor(final String key) { final Property property = getMap().findProperty(key); ! final GlobalObject global = (GlobalObject)Context.getGlobalTrusted(); if (property != null) { final ScriptFunction get = property.getGetterFunction(this); final ScriptFunction set = property.getSetterFunction(this); --- 372,382 ---- * object, or undefined if absent. */ public Object getOwnPropertyDescriptor(final String key) { final Property property = getMap().findProperty(key); ! final Global global = Context.getGlobal(); if (property != null) { final ScriptFunction get = property.getGetterFunction(this); final ScriptFunction set = property.getSetterFunction(this);
*** 438,448 **** * @param reject is the property extensible - true means new definitions are rejected * * @return true if property was successfully defined */ public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) { ! final ScriptObject global = Context.getGlobalTrusted(); final PropertyDescriptor desc = toPropertyDescriptor(global, propertyDesc); final Object current = getOwnPropertyDescriptor(key); final String name = JSType.toString(key); if (current == UNDEFINED) { --- 437,447 ---- * @param reject is the property extensible - true means new definitions are rejected * * @return true if property was successfully defined */ public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) { ! final Global global = Context.getGlobal(); final PropertyDescriptor desc = toPropertyDescriptor(global, propertyDesc); final Object current = getOwnPropertyDescriptor(key); final String name = JSType.toString(key); if (current == UNDEFINED) {
*** 465,475 **** ! newDesc.has(CONFIGURABLE) && ! newDesc.has(ENUMERABLE)) { // every descriptor field is absent return true; } ! if (currentDesc.equals(newDesc)) { // every descriptor field of the new is same as the current return true; } if (! currentDesc.isConfigurable()) { --- 464,474 ---- ! newDesc.has(CONFIGURABLE) && ! newDesc.has(ENUMERABLE)) { // every descriptor field is absent return true; } ! if (newDesc.hasAndEquals(currentDesc)) { // every descriptor field of the new is same as the current return true; } if (! currentDesc.isConfigurable()) {
*** 636,646 **** PropertyDescriptor pdesc = propertyDesc; final int propFlags = Property.toFlags(pdesc); if (pdesc.type() == PropertyDescriptor.GENERIC) { ! final GlobalObject global = (GlobalObject) Context.getGlobalTrusted(); final PropertyDescriptor dDesc = global.newDataDescriptor(UNDEFINED, false, false, false); dDesc.fillFrom((ScriptObject)pdesc); pdesc = dDesc; } --- 635,645 ---- PropertyDescriptor pdesc = propertyDesc; final int propFlags = Property.toFlags(pdesc); if (pdesc.type() == PropertyDescriptor.GENERIC) { ! final Global global = Context.getGlobal(); final PropertyDescriptor dDesc = global.newDataDescriptor(UNDEFINED, false, false, false); dDesc.fillFrom((ScriptObject)pdesc); pdesc = dDesc; }
*** 871,882 **** // erase old property value and create new user accessor property erasePropertyValue(oldProperty); newProperty = newUserAccessors(oldProperty.getKey(), propertyFlags, getter, setter); } - notifyPropertyModified(this, oldProperty, newProperty); - return modifyOwnProperty(oldProperty, newProperty); } /** * Modify a property in the object --- 870,879 ----
*** 979,1009 **** return ObjectClassGenerator.UNDEFINED_DOUBLE; } /** - * Get the object value of a property - * - * @param find {@link FindProperty} lookup result - * - * @return the value of the property - */ - protected static Object getObjectValue(final FindProperty find) { - return find.getObjectValue(); - } - - /** * Return methodHandle of value function for call. * * @param find data from find property. * @param type method type of function. * @param bindName null or name to bind to second argument (property not found method.) * * @return value of property as a MethodHandle or null. */ protected MethodHandle getCallMethodHandle(final FindProperty find, final MethodType type, final String bindName) { ! return getCallMethodHandle(getObjectValue(find), type, bindName); } /** * Return methodHandle of value function for call. * --- 976,995 ---- return ObjectClassGenerator.UNDEFINED_DOUBLE; } /** * Return methodHandle of value function for call. * * @param find data from find property. * @param type method type of function. * @param bindName null or name to bind to second argument (property not found method.) * * @return value of property as a MethodHandle or null. */ protected MethodHandle getCallMethodHandle(final FindProperty find, final MethodType type, final String bindName) { ! return getCallMethodHandle(find.getObjectValue(), type, bindName); } /** * Return methodHandle of value function for call. *
*** 1023,1033 **** * @param property Found property. * * @return Value of property. */ public final Object getWithProperty(final Property property) { ! return getObjectValue(new FindProperty(this, this, property)); } /** * Get a property given a key * --- 1009,1019 ---- * @param property Found property. * * @return Value of property. */ public final Object getWithProperty(final Property property) { ! return new FindProperty(this, this, property).getObjectValue(); } /** * Get a property given a key *
*** 1116,1145 **** * Set the __proto__ of an object. * @param newProto new __proto__ to set. */ public synchronized final void setProto(final ScriptObject newProto) { final ScriptObject oldProto = proto; - map = map.changeProto(oldProto, newProto); - - if (newProto != null) { - newProto.setIsPrototype(); - } proto = newProto; ! if (isPrototype()) { ! // tell listeners that my __proto__ has been changed ! notifyProtoChanged(this, oldProto, newProto); ! ! if (oldProto != null) { ! oldProto.removePropertyListener(this); } ! ! if (newProto != null) { ! newProto.addPropertyListener(this); } } } /** * Set the __proto__ of an object with checks. * @param newProto Prototype to set. --- 1102,1135 ---- * Set the __proto__ of an object. * @param newProto new __proto__ to set. */ public synchronized final void setProto(final ScriptObject newProto) { final ScriptObject oldProto = proto; + if (oldProto != newProto) { proto = newProto; ! // Let current listeners know that the protototype has changed and set our map ! final PropertyListeners listeners = getMap().getListeners(); ! if (listeners != null) { ! listeners.protoChanged(); } ! // Replace our current allocator map with one that is associated with the new prototype. ! setMap(getMap().changeProto(newProto)); } } + + /** + * Set the initial __proto__ of this object. This should be used instead of + * {@link #setProto} if it is known that the current property map will not be + * used on a new object with any other parent property map, so we can pass over + * property map invalidation/evolution. + * + * @param initialProto the initial __proto__ to set. + */ + public void setInitialProto(final ScriptObject initialProto) { + this.proto = initialProto; } /** * Set the __proto__ of an object with checks. * @param newProto Prototype to set.
*** 1158,1168 **** } p = p.getProto(); } setProto((ScriptObject)newProto); } else { ! final ScriptObject global = Context.getGlobalTrusted(); final Object newProtoObject = JSType.toScriptObject(global, newProto); if (newProtoObject instanceof ScriptObject) { setProto((ScriptObject)newProtoObject); } else { --- 1148,1158 ---- } p = p.getProto(); } setProto((ScriptObject)newProto); } else { ! final Global global = Context.getGlobal(); final Object newProtoObject = JSType.toScriptObject(global, newProto); if (newProtoObject instanceof ScriptObject) { setProto((ScriptObject)newProtoObject); } else {
*** 1248,1262 **** * * @param typeHint the preferred type hint * @return the default value */ public Object getDefaultValue(final Class<?> typeHint) { ! // We delegate to GlobalObject, as the implementation uses dynamic call sites to invoke object's "toString" and // "valueOf" methods, and in order to avoid those call sites from becoming megamorphic when multiple contexts // are being executed in a long-running program, we move the code and their associated dynamic call sites // (Global.TO_STRING and Global.VALUE_OF) into per-context code. ! return ((GlobalObject)Context.getGlobalTrusted()).getDefaultValue(this, typeHint); } /** * Checking whether a script object is an instance of another. Used * in {@link ScriptFunction} for hasInstance implementation, walks --- 1238,1252 ---- * * @param typeHint the preferred type hint * @return the default value */ public Object getDefaultValue(final Class<?> typeHint) { ! // We delegate to Global, as the implementation uses dynamic call sites to invoke object's "toString" and // "valueOf" methods, and in order to avoid those call sites from becoming megamorphic when multiple contexts // are being executed in a long-running program, we move the code and their associated dynamic call sites // (Global.TO_STRING and Global.VALUE_OF) into per-context code. ! return Context.getGlobal().getDefaultValue(this, typeHint); } /** * Checking whether a script object is an instance of another. Used * in {@link ScriptFunction} for hasInstance implementation, walks
*** 1328,1356 **** public final void setIsArguments() { flags |= IS_ARGUMENTS; } /** - * Check if this object is a prototype - * - * @return {@code true} if is prototype - */ - public final boolean isPrototype() { - return (flags & IS_PROTOTYPE) != 0; - } - - /** - * Flag this object as having a prototype. - */ - public final void setIsPrototype() { - if (proto != null && !isPrototype()) { - proto.addPropertyListener(this); - } - flags |= IS_PROTOTYPE; - } - - /** * Check if this object has non-writable length property * * @return {@code true} if 'length' property is non-writable */ public final boolean isLengthNotWritable() { --- 1318,1327 ----
*** 1710,1731 **** // Fold Object(P0, P1, ...) into R(Object, P0, P1, ...) => R(P0, P1, ...) return getter.replaceMethods(MH.foldArguments(invoker, argDroppingGetter), getter.getGuard()); } /** * Find the appropriate GET method for an invoke dynamic call. * * @param desc the call site descriptor * @param request the link request * @param operator operator for get: getProp, getMethod, getElem etc * * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); ! if (request.isCallSiteUnstable()) { ! return findMegaMorphicGetMethod(desc, name, "getMethod".equals(operator)); } final FindProperty find = findProperty(name, true); MethodHandle methodHandle; --- 1681,1740 ---- // Fold Object(P0, P1, ...) into R(Object, P0, P1, ...) => R(P0, P1, ...) return getter.replaceMethods(MH.foldArguments(invoker, argDroppingGetter), getter.getGuard()); } /** + * Test whether this object contains in its prototype chain or is itself a with-object. + * @return true if a with-object was found + */ + final boolean hasWithScope() { + if (isScope()) { + for (ScriptObject obj = this; obj != null; obj = obj.getProto()) { + if (obj instanceof WithObject) { + return true; + } + } + } + return false; + } + + /** + * Add a filter to the first argument of {@code methodHandle} that calls its {@link #getProto()} method + * {@code depth} times. + * @param methodHandle a method handle + * @param depth distance to target prototype + * @return the filtered method handle + */ + static MethodHandle addProtoFilter(final MethodHandle methodHandle, final int depth) { + if (depth == 0) { + return methodHandle; + } + final int listIndex = depth - 1; // We don't need 0-deep walker + MethodHandle filter = listIndex < protoFilters.size() ? protoFilters.get(listIndex) : null; + + if(filter == null) { + filter = addProtoFilter(GETPROTO, depth - 1); + protoFilters.add(null); + protoFilters.set(listIndex, filter); + } + + return MH.filterArguments(methodHandle, 0, filter.asType(filter.type().changeReturnType(methodHandle.type().parameterType(0)))); + } + + /** * Find the appropriate GET method for an invoke dynamic call. * * @param desc the call site descriptor * @param request the link request * @param operator operator for get: getProp, getMethod, getElem etc * * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) { final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); ! if (request.isCallSiteUnstable() || hasWithScope()) { ! return findMegaMorphicGetMethod(desc, name, "getMethod".equals(operator), isScope() && NashornCallSiteDescriptor.isScope(desc)); } final FindProperty find = findProperty(name, true); MethodHandle methodHandle;
*** 1746,1789 **** final Class<?> returnType = desc.getMethodType().returnType(); final Property property = find.getProperty(); methodHandle = find.getGetter(returnType); ! // getMap() is fine as we have the prototype switchpoint depending on where the property was found ! final MethodHandle guard = NashornGuards.getMapGuard(getMap()); if (methodHandle != null) { assert methodHandle.type().returnType().equals(returnType); if (find.isSelf()) { ! return new GuardedInvocation(methodHandle, ObjectClassGenerator.OBJECT_FIELDS_ONLY && ! NashornCallSiteDescriptor.isFastScope(desc) && !property.canChangeType() ? null : guard); } ! final ScriptObject prototype = find.getOwner(); ! ! if (!property.hasGetterFunction(prototype)) { ! methodHandle = bindTo(methodHandle, prototype); } ! return new GuardedInvocation(methodHandle, getMap().getProtoGetSwitchPoint(proto, name), guard); } assert !NashornCallSiteDescriptor.isFastScope(desc); ! return new GuardedInvocation(Lookup.emptyGetter(returnType), getMap().getProtoGetSwitchPoint(proto, name), guard); } ! private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name, final boolean isMethod) { ! final MethodHandle invoker = MH.insertArguments(MEGAMORPHIC_GET, 1, name, isMethod); final MethodHandle guard = getScriptObjectGuard(desc.getMethodType()); return new GuardedInvocation(invoker, guard); } @SuppressWarnings("unused") ! private Object megamorphicGet(final String key, final boolean isMethod) { final FindProperty find = findProperty(key, true); if (find != null) { ! return getObjectValue(find); } return isMethod ? getNoSuchMethod(key) : invokeNoSuchProperty(key); } --- 1755,1801 ---- final Class<?> returnType = desc.getMethodType().returnType(); final Property property = find.getProperty(); methodHandle = find.getGetter(returnType); ! // Get the appropriate guard for this callsite and property. ! final MethodHandle guard = NashornGuards.getGuard(this, property, desc); ! final ScriptObject owner = find.getOwner(); if (methodHandle != null) { assert methodHandle.type().returnType().equals(returnType); if (find.isSelf()) { ! return new GuardedInvocation(methodHandle, guard); } ! if (!property.hasGetterFunction(owner)) { ! // Add a filter that replaces the self object with the prototype owning the property. ! methodHandle = addProtoFilter(methodHandle, find.getProtoChainLength()); } ! return new GuardedInvocation(methodHandle, guard == null ? null : getProtoSwitchPoint(name, owner), guard); } assert !NashornCallSiteDescriptor.isFastScope(desc); ! return new GuardedInvocation(Lookup.emptyGetter(returnType), getProtoSwitchPoint(name, owner), guard); } ! private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name, ! final boolean isMethod, final boolean isScope) { ! final MethodHandle invoker = MH.insertArguments(MEGAMORPHIC_GET, 1, name, isMethod, isScope); final MethodHandle guard = getScriptObjectGuard(desc.getMethodType()); return new GuardedInvocation(invoker, guard); } @SuppressWarnings("unused") ! private Object megamorphicGet(final String key, final boolean isMethod, final boolean isScope) { final FindProperty find = findProperty(key, true); if (find != null) { ! return find.getObjectValue(); ! } ! if (isScope) { ! throw referenceError("not.defined", key); } return isMethod ? getNoSuchMethod(key) : invokeNoSuchProperty(key); }
*** 1822,1841 **** private static MethodHandle getScriptObjectGuard(final MethodType type) { return ScriptObject.class.isAssignableFrom(type.parameterType(0)) ? null : NashornGuards.getScriptObjectGuard(); } /** * Find the appropriate SET method for an invoke dynamic call. * * @param desc the call site descriptor * @param request the link request * * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); ! if (request.isCallSiteUnstable()) { return findMegaMorphicSetMethod(desc, name); } final boolean scope = isScope(); /* --- 1834,1875 ---- private static MethodHandle getScriptObjectGuard(final MethodType type) { return ScriptObject.class.isAssignableFrom(type.parameterType(0)) ? null : NashornGuards.getScriptObjectGuard(); } /** + * Get a switch point for a property with the given {@code name} that will be invalidated when + * the property definition is changed in this object's prototype chain. Returns {@code null} if + * the property is defined in this object itself. + * + * @param name the property name + * @param owner the property owner, null if property is not defined + * @return a SwitchPoint or null + */ + public final SwitchPoint getProtoSwitchPoint(final String name, final ScriptObject owner) { + if (owner == this || getProto() == null) { + return null; + } + + for (ScriptObject obj = this; obj != owner && obj.getProto() != null; obj = obj.getProto()) { + ScriptObject parent = obj.getProto(); + parent.getMap().addListener(name, obj.getMap()); + } + + return getMap().getSwitchPoint(name); + } + + /** * Find the appropriate SET method for an invoke dynamic call. * * @param desc the call site descriptor * @param request the link request * * @return GuardedInvocation to be invoked at call site. */ protected GuardedInvocation findSetMethod(final CallSiteDescriptor desc, final LinkRequest request) { final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); ! if (request.isCallSiteUnstable() || hasWithScope()) { return findMegaMorphicSetMethod(desc, name); } final boolean scope = isScope(); /*
*** 1877,1896 **** final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); if (NashornCallSiteDescriptor.isStrict(desc)) { throw typeError(strictErrorMessage, name, ScriptRuntime.safeToString((this))); } assert canBeFastScope || !NashornCallSiteDescriptor.isFastScope(desc); ! final PropertyMap myMap = getMap(); ! return new GuardedInvocation(Lookup.EMPTY_SETTER, myMap.getProtoGetSwitchPoint(proto, name), NashornGuards.getMapGuard(myMap)); } @SuppressWarnings("unused") private static void setField(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final MethodHandle setter, final Object self, final Object value) throws Throwable { final ScriptObject obj = (ScriptObject)self; final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); if (!obj.isExtensible()) { throw typeError("object.non.extensible", desc.getNameToken(2), ScriptRuntime.safeToString(obj)); } else if (obj.compareAndSetMap(oldMap, newMap)) { setter.invokeExact(self, value); } else { obj.set(desc.getNameToken(CallSiteDescriptor.NAME_OPERAND), value, isStrict); } --- 1911,1931 ---- final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); if (NashornCallSiteDescriptor.isStrict(desc)) { throw typeError(strictErrorMessage, name, ScriptRuntime.safeToString((this))); } assert canBeFastScope || !NashornCallSiteDescriptor.isFastScope(desc); ! return new GuardedInvocation(Lookup.EMPTY_SETTER, getProtoSwitchPoint(name, null), NashornGuards.getMapGuard(getMap())); } @SuppressWarnings("unused") private static void setField(final CallSiteDescriptor desc, final PropertyMap oldMap, final PropertyMap newMap, final MethodHandle setter, final Object self, final Object value) throws Throwable { final ScriptObject obj = (ScriptObject)self; final boolean isStrict = NashornCallSiteDescriptor.isStrict(desc); if (!obj.isExtensible()) { + if (isStrict) { throw typeError("object.non.extensible", desc.getNameToken(2), ScriptRuntime.safeToString(obj)); + } } else if (obj.compareAndSetMap(oldMap, newMap)) { setter.invokeExact(self, value); } else { obj.set(desc.getNameToken(CallSiteDescriptor.NAME_OPERAND), value, isStrict); }
*** 1951,1960 **** --- 1986,2004 ---- } else { obj.set(desc.getNameToken(2), value, isStrict); } } + @SuppressWarnings("unused") + private static Object globalFilter(final Object object) { + ScriptObject sobj = (ScriptObject) object; + while (sobj != null && !(sobj instanceof Global)) { + sobj = sobj.getProto(); + } + return sobj; + } + private static GuardedInvocation findMegaMorphicSetMethod(final CallSiteDescriptor desc, final String name) { final MethodType type = desc.getMethodType().insertParameterTypes(1, Object.class); final GuardedInvocation inv = findSetIndexMethod(type, NashornCallSiteDescriptor.isStrict(desc)); return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard()); }
*** 1996,2006 **** if (find == null) { return noSuchProperty(desc, request); } ! final Object value = getObjectValue(find); if (! (value instanceof ScriptFunction)) { return createEmptyGetter(desc, name); } final ScriptFunction func = (ScriptFunction)value; --- 2040,2050 ---- if (find == null) { return noSuchProperty(desc, request); } ! final Object value = find.getObjectValue(); if (! (value instanceof ScriptFunction)) { return createEmptyGetter(desc, name); } final ScriptFunction func = (ScriptFunction)value;
*** 2022,2032 **** final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc); if (find != null) { ! final Object value = getObjectValue(find); ScriptFunction func = null; MethodHandle methodHandle = null; if (value instanceof ScriptFunction) { func = (ScriptFunction)value; --- 2066,2076 ---- final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND); final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc); if (find != null) { ! final Object value = find.getObjectValue(); ScriptFunction func = null; MethodHandle methodHandle = null; if (value instanceof ScriptFunction) { func = (ScriptFunction)value;
*** 2036,2066 **** if (methodHandle != null) { if (scopeAccess && func.isStrict()) { methodHandle = bindTo(methodHandle, UNDEFINED); } return new GuardedInvocation(methodHandle, ! find.isInherited()? getMap().getProtoGetSwitchPoint(proto, NO_SUCH_PROPERTY_NAME) : null, getKnownFunctionPropertyGuard(getMap(), find.getGetter(Object.class), find.getOwner(), func)); } } if (scopeAccess) { throw referenceError("not.defined", name); } return createEmptyGetter(desc, name); } /** * Invoke fall back if a property is not found. * @param name Name of property. * @return Result from call. */ ! private Object invokeNoSuchProperty(final String name) { final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); if (find != null) { ! final Object func = getObjectValue(find); if (func instanceof ScriptFunction) { return ScriptRuntime.apply((ScriptFunction)func, this, name); } } --- 2080,2111 ---- if (methodHandle != null) { if (scopeAccess && func.isStrict()) { methodHandle = bindTo(methodHandle, UNDEFINED); } return new GuardedInvocation(methodHandle, ! getProtoSwitchPoint(NO_SUCH_PROPERTY_NAME, find.getOwner()), getKnownFunctionPropertyGuard(getMap(), find.getGetter(Object.class), find.getOwner(), func)); } } if (scopeAccess) { throw referenceError("not.defined", name); } return createEmptyGetter(desc, name); } + /** * Invoke fall back if a property is not found. * @param name Name of property. * @return Result from call. */ ! protected Object invokeNoSuchProperty(final String name) { final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true); if (find != null) { ! final Object func = find.getObjectValue(); if (func instanceof ScriptFunction) { return ScriptRuntime.apply((ScriptFunction)func, this, name); } }
*** 2078,2097 **** if (find == null) { return invokeNoSuchProperty(name); } ! final Object value = getObjectValue(find); if (! (value instanceof ScriptFunction)) { return UNDEFINED; } return ((ScriptFunction)value).makeBoundFunction(this, new Object[] {name}); } private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) { ! return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), getMap().getProtoGetSwitchPoint(proto, name), NashornGuards.getMapGuard(getMap())); } private abstract static class ScriptObjectIterator <T extends Object> implements Iterator<T> { protected T[] values; protected final ScriptObject object; --- 2123,2143 ---- if (find == null) { return invokeNoSuchProperty(name); } ! final Object value = find.getObjectValue(); if (! (value instanceof ScriptFunction)) { return UNDEFINED; } return ((ScriptFunction)value).makeBoundFunction(this, new Object[] {name}); } private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) { ! return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), ! getProtoSwitchPoint(name, null), NashornGuards.getMapGuard(getMap())); } private abstract static class ScriptObjectIterator <T extends Object> implements Iterator<T> { protected T[] values; protected final ScriptObject object;
*** 2168,2183 **** int fieldMaximum = getMap().getFieldMaximum(); Property property; if (fieldCount < fieldMaximum) { property = new AccessorProperty(key, propertyFlags & ~Property.IS_SPILL, getClass(), fieldCount); - notifyPropertyAdded(this, property); property = addOwnProperty(property); } else { int i = getMap().getSpillLength(); property = new AccessorProperty(key, propertyFlags | Property.IS_SPILL, i); - notifyPropertyAdded(this, property); property = addOwnProperty(property); i = property.getSlot(); final int newLength = (i + SPILL_RATE) / SPILL_RATE * SPILL_RATE; --- 2214,2227 ----
*** 2619,2629 **** for (ScriptObject object = this; ; ) { if (object.getMap().containsArrayKeys()) { final FindProperty find = object.findProperty(key, false, false, this); if (find != null) { ! return getObjectValue(find); } } if ((object = object.getProto()) == null) { break; --- 2663,2673 ---- for (ScriptObject object = this; ; ) { if (object.getMap().containsArrayKeys()) { final FindProperty find = object.findProperty(key, false, false, this); if (find != null) { ! return find.getObjectValue(); } } if ((object = object.getProto()) == null) { break;
*** 2637,2647 **** } } else { final FindProperty find = findProperty(key, true); if (find != null) { ! return getObjectValue(find); } } return invokeNoSuchProperty(key); } --- 2681,2691 ---- } } else { final FindProperty find = findProperty(key, true); if (find != null) { ! return find.getObjectValue(); } } return invokeNoSuchProperty(key); }
*** 2757,2767 **** * @param value property value */ public final void setObject(final FindProperty find, final boolean strict, final String key, final Object value) { FindProperty f = find; ! if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty)) { f = null; } if (f != null) { if (!f.getProperty().isWritable()) { --- 2801,2812 ---- * @param value property value */ public final void setObject(final FindProperty find, final boolean strict, final String key, final Object value) { FindProperty f = find; ! if (f != null && f.isInherited() && !(f.getProperty() instanceof UserAccessorProperty) && !isScope()) { ! // Setting a property should not modify the property in prototype unless this is a scope object. f = null; } if (f != null) { if (!f.getProperty().isWritable()) {
*** 2777,2787 **** } else if (!isExtensible()) { if (strict) { throw typeError("object.non.extensible", key, ScriptRuntime.safeToString(this)); } } else { ! spill(key, value); } } private void spill(final String key, final Object value) { addSpillProperty(key, 0).setObjectValue(this, this, value, false); --- 2822,2840 ---- } else if (!isExtensible()) { if (strict) { throw typeError("object.non.extensible", key, ScriptRuntime.safeToString(this)); } } else { ! ScriptObject sobj = this; ! // undefined scope properties are set in the global object. ! if (isScope()) { ! while (sobj != null && !(sobj instanceof Global)) { ! sobj = sobj.getProto(); ! } ! assert sobj != null : "no parent global object in scope"; ! } ! sobj.spill(key, value); } } private void spill(final String key, final Object value) { addSpillProperty(key, 0).setObjectValue(this, this, value, false);
*** 3226,3236 **** } return false; } final Property prop = find.getProperty(); - notifyPropertyDeleted(this, prop); deleteOwnProperty(prop); return true; } --- 3279,3288 ----