Thu, 04 Sep 2014 18:47:18 +0200
8051889: Implement block scoping in symbol assignment and scope computation
Reviewed-by: attila, lagergren
jlaskey@3 | 1 | /* |
jlaskey@7 | 2 | * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
jlaskey@3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
jlaskey@3 | 4 | * |
jlaskey@3 | 5 | * This code is free software; you can redistribute it and/or modify it |
jlaskey@3 | 6 | * under the terms of the GNU General Public License version 2 only, as |
jlaskey@3 | 7 | * published by the Free Software Foundation. Oracle designates this |
jlaskey@3 | 8 | * particular file as subject to the "Classpath" exception as provided |
jlaskey@3 | 9 | * by Oracle in the LICENSE file that accompanied this code. |
jlaskey@3 | 10 | * |
jlaskey@3 | 11 | * This code is distributed in the hope that it will be useful, but WITHOUT |
jlaskey@3 | 12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
jlaskey@3 | 13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
jlaskey@3 | 14 | * version 2 for more details (a copy is included in the LICENSE file that |
jlaskey@3 | 15 | * accompanied this code). |
jlaskey@3 | 16 | * |
jlaskey@3 | 17 | * You should have received a copy of the GNU General Public License version |
jlaskey@3 | 18 | * 2 along with this work; if not, write to the Free Software Foundation, |
jlaskey@3 | 19 | * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
jlaskey@3 | 20 | * |
jlaskey@3 | 21 | * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
jlaskey@3 | 22 | * or visit www.oracle.com if you need additional information or have any |
jlaskey@3 | 23 | * questions. |
jlaskey@3 | 24 | */ |
jlaskey@3 | 25 | |
jlaskey@3 | 26 | package jdk.nashorn.internal.runtime; |
jlaskey@3 | 27 | |
lagergren@96 | 28 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; |
attila@963 | 29 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE; |
lagergren@96 | 30 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter; |
lagergren@96 | 31 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter; |
attila@963 | 32 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldCount; |
attila@963 | 33 | import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName; |
sundar@133 | 34 | import static jdk.nashorn.internal.lookup.Lookup.MH; |
sundar@133 | 35 | import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName; |
attila@963 | 36 | import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex; |
attila@963 | 37 | import static jdk.nashorn.internal.runtime.JSType.getNumberOfAccessorTypes; |
attila@963 | 38 | import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; |
jlaskey@3 | 39 | |
hannesw@828 | 40 | import java.io.IOException; |
hannesw@828 | 41 | import java.io.ObjectInputStream; |
jlaskey@3 | 42 | import java.lang.invoke.MethodHandle; |
jlaskey@3 | 43 | import java.lang.invoke.MethodHandles; |
attila@963 | 44 | import java.lang.invoke.SwitchPoint; |
attila@963 | 45 | import java.util.function.Supplier; |
attila@963 | 46 | import java.util.logging.Level; |
lagergren@96 | 47 | import jdk.nashorn.internal.codegen.ObjectClassGenerator; |
jlaskey@3 | 48 | import jdk.nashorn.internal.codegen.types.Type; |
sundar@133 | 49 | import jdk.nashorn.internal.lookup.Lookup; |
attila@963 | 50 | import jdk.nashorn.internal.objects.Global; |
jlaskey@3 | 51 | |
jlaskey@3 | 52 | /** |
jlaskey@3 | 53 | * An AccessorProperty is the most generic property type. An AccessorProperty is |
jlaskey@3 | 54 | * represented as fields in a ScriptObject class. |
jlaskey@3 | 55 | */ |
attila@963 | 56 | public class AccessorProperty extends Property { |
attila@963 | 57 | private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); |
attila@963 | 58 | |
attila@963 | 59 | private static final MethodHandle REPLACE_MAP = findOwnMH_S("replaceMap", Object.class, Object.class, PropertyMap.class); |
attila@963 | 60 | private static final MethodHandle INVALIDATE_SP = findOwnMH_S("invalidateSwitchPoint", Object.class, Object.class, SwitchPoint.class); |
attila@963 | 61 | |
attila@963 | 62 | private static final SwitchPoint NO_CHANGE_CALLBACK = new SwitchPoint(); |
jlaskey@3 | 63 | |
jlaskey@3 | 64 | private static final int NOOF_TYPES = getNumberOfAccessorTypes(); |
hannesw@828 | 65 | private static final long serialVersionUID = 3371720170182154920L; |
jlaskey@3 | 66 | |
attila@224 | 67 | /** |
attila@224 | 68 | * Properties in different maps for the same structure class will share their field getters and setters. This could |
attila@224 | 69 | * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now |
attila@224 | 70 | * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler |
attila@224 | 71 | * for them. |
attila@224 | 72 | */ |
attila@963 | 73 | private static ClassValue<Accessors> GETTERS_SETTERS = new ClassValue<Accessors>() { |
attila@224 | 74 | @Override |
attila@963 | 75 | protected Accessors computeValue(final Class<?> structure) { |
attila@963 | 76 | return new Accessors(structure); |
attila@224 | 77 | } |
attila@224 | 78 | }; |
attila@224 | 79 | |
attila@963 | 80 | private static class Accessors { |
attila@963 | 81 | final MethodHandle[] objectGetters; |
attila@963 | 82 | final MethodHandle[] objectSetters; |
attila@963 | 83 | final MethodHandle[] primitiveGetters; |
attila@963 | 84 | final MethodHandle[] primitiveSetters; |
jlaskey@3 | 85 | |
attila@963 | 86 | /** |
attila@963 | 87 | * Normal |
attila@963 | 88 | * @param structure |
attila@963 | 89 | */ |
attila@963 | 90 | Accessors(final Class<?> structure) { |
attila@963 | 91 | final int fieldCount = getFieldCount(structure); |
attila@963 | 92 | objectGetters = new MethodHandle[fieldCount]; |
attila@963 | 93 | objectSetters = new MethodHandle[fieldCount]; |
attila@963 | 94 | primitiveGetters = new MethodHandle[fieldCount]; |
attila@963 | 95 | primitiveSetters = new MethodHandle[fieldCount]; |
hannesw@291 | 96 | |
attila@963 | 97 | for (int i = 0; i < fieldCount; i++) { |
attila@963 | 98 | final String fieldName = getFieldName(i, Type.OBJECT); |
attila@963 | 99 | final Class<?> typeClass = Type.OBJECT.getTypeClass(); |
attila@963 | 100 | objectGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldName, typeClass), Lookup.GET_OBJECT_TYPE); |
attila@963 | 101 | objectSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldName, typeClass), Lookup.SET_OBJECT_TYPE); |
attila@963 | 102 | } |
hannesw@291 | 103 | |
attila@963 | 104 | if (!OBJECT_FIELDS_ONLY) { |
attila@963 | 105 | for (int i = 0; i < fieldCount; i++) { |
attila@963 | 106 | final String fieldNamePrimitive = getFieldName(i, PRIMITIVE_FIELD_TYPE); |
attila@963 | 107 | final Class<?> typeClass = PRIMITIVE_FIELD_TYPE.getTypeClass(); |
attila@963 | 108 | primitiveGetters[i] = MH.asType(MH.getter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.GET_PRIMITIVE_TYPE); |
attila@963 | 109 | primitiveSetters[i] = MH.asType(MH.setter(LOOKUP, structure, fieldNamePrimitive, typeClass), Lookup.SET_PRIMITIVE_TYPE); |
attila@963 | 110 | } |
jlaskey@410 | 111 | } |
hannesw@291 | 112 | } |
attila@963 | 113 | } |
hannesw@291 | 114 | |
attila@963 | 115 | /** |
attila@963 | 116 | * Property getter cache |
attila@963 | 117 | * Note that we can't do the same simple caching for optimistic getters, |
attila@963 | 118 | * due to the fact that they are bound to a program point, which will |
attila@963 | 119 | * produce different boun method handles wrapping the same access mechanism |
attila@963 | 120 | * depending on callsite |
attila@963 | 121 | */ |
attila@963 | 122 | private MethodHandle[] GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
jlaskey@3 | 123 | |
sundar@418 | 124 | /** |
sundar@418 | 125 | * Create a new accessor property. Factory method used by nasgen generated code. |
sundar@418 | 126 | * |
sundar@418 | 127 | * @param key {@link Property} key. |
sundar@418 | 128 | * @param propertyFlags {@link Property} flags. |
sundar@418 | 129 | * @param getter {@link Property} get accessor method. |
sundar@418 | 130 | * @param setter {@link Property} set accessor method. |
sundar@418 | 131 | * |
sundar@418 | 132 | * @return New {@link AccessorProperty} created. |
sundar@418 | 133 | */ |
sundar@418 | 134 | public static AccessorProperty create(final String key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) { |
sundar@418 | 135 | return new AccessorProperty(key, propertyFlags, -1, getter, setter); |
sundar@418 | 136 | } |
sundar@418 | 137 | |
jlaskey@3 | 138 | /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
attila@963 | 139 | transient MethodHandle primitiveGetter; |
jlaskey@3 | 140 | |
jlaskey@3 | 141 | /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */ |
attila@963 | 142 | transient MethodHandle primitiveSetter; |
jlaskey@3 | 143 | |
jlaskey@3 | 144 | /** Seed getter for the Object version of this field */ |
attila@963 | 145 | transient MethodHandle objectGetter; |
jlaskey@3 | 146 | |
jlaskey@3 | 147 | /** Seed setter for the Object version of this field */ |
attila@963 | 148 | transient MethodHandle objectSetter; |
jlaskey@3 | 149 | |
jlaskey@3 | 150 | /** |
jlaskey@3 | 151 | * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode |
jlaskey@3 | 152 | * null means undefined, and primitive types are allowed. The reason a special type is used for |
jlaskey@3 | 153 | * undefined, is that are no bits left to represent it in primitive types |
jlaskey@3 | 154 | */ |
jlaskey@3 | 155 | private Class<?> currentType; |
jlaskey@3 | 156 | |
jlaskey@3 | 157 | /** |
hannesw@769 | 158 | * Delegate constructor for bound properties. This is used for properties created by |
hannesw@769 | 159 | * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method. |
hannesw@769 | 160 | * The former is used to add a script's defined globals to the current global scope while |
hannesw@769 | 161 | * still storing them in a JO-prefixed ScriptObject class. |
hannesw@769 | 162 | * |
hannesw@769 | 163 | * <p>All properties created by this constructor have the {@link #IS_BOUND} flag set.</p> |
jlaskey@3 | 164 | * |
jlaskey@3 | 165 | * @param property accessor property to rebind |
sundar@423 | 166 | * @param delegate delegate object to rebind receiver to |
jlaskey@3 | 167 | */ |
sundar@476 | 168 | AccessorProperty(final AccessorProperty property, final Object delegate) { |
attila@963 | 169 | super(property, property.getFlags() | IS_BOUND); |
jlaskey@3 | 170 | |
hannesw@291 | 171 | this.primitiveGetter = bindTo(property.primitiveGetter, delegate); |
hannesw@291 | 172 | this.primitiveSetter = bindTo(property.primitiveSetter, delegate); |
attila@963 | 173 | this.objectGetter = bindTo(property.objectGetter, delegate); |
attila@963 | 174 | this.objectSetter = bindTo(property.objectSetter, delegate); |
attila@963 | 175 | property.GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
hannesw@769 | 176 | // Properties created this way are bound to a delegate |
jlaskey@3 | 177 | setCurrentType(property.getCurrentType()); |
jlaskey@3 | 178 | } |
jlaskey@3 | 179 | |
jlaskey@3 | 180 | /** |
attila@963 | 181 | * SPILL PROPERTY or USER ACCESSOR PROPERTY abstract constructor |
attila@963 | 182 | * |
hannesw@291 | 183 | * Constructor for spill properties. Array getters and setters will be created on demand. |
hannesw@291 | 184 | * |
hannesw@291 | 185 | * @param key the property key |
hannesw@291 | 186 | * @param flags the property flags |
hannesw@291 | 187 | * @param slot spill slot |
attila@963 | 188 | * @param primitiveGetter primitive getter |
attila@963 | 189 | * @param primitiveSetter primitive setter |
attila@963 | 190 | * @param objectGetter object getter |
attila@963 | 191 | * @param objectSetter object setter |
hannesw@291 | 192 | */ |
attila@963 | 193 | protected AccessorProperty( |
attila@963 | 194 | final String key, |
attila@963 | 195 | final int flags, |
attila@963 | 196 | final int slot, |
attila@963 | 197 | final MethodHandle primitiveGetter, |
attila@963 | 198 | final MethodHandle primitiveSetter, |
attila@963 | 199 | final MethodHandle objectGetter, |
attila@963 | 200 | final MethodHandle objectSetter) { |
hannesw@291 | 201 | super(key, flags, slot); |
attila@963 | 202 | assert getClass() != AccessorProperty.class; |
attila@963 | 203 | this.primitiveGetter = primitiveGetter; |
attila@963 | 204 | this.primitiveSetter = primitiveSetter; |
attila@963 | 205 | this.objectGetter = objectGetter; |
attila@963 | 206 | this.objectSetter = objectSetter; |
attila@963 | 207 | initializeType(); |
hannesw@291 | 208 | } |
hannesw@291 | 209 | |
hannesw@291 | 210 | /** |
attila@963 | 211 | * NASGEN constructor |
attila@963 | 212 | * |
jlaskey@3 | 213 | * Constructor. Similar to the constructor with both primitive getters and setters, the difference |
jlaskey@3 | 214 | * here being that only one getter and setter (setter is optional for non writable fields) is given |
jlaskey@3 | 215 | * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes |
jlaskey@3 | 216 | * |
jlaskey@3 | 217 | * @param key the property key |
jlaskey@3 | 218 | * @param flags the property flags |
jlaskey@80 | 219 | * @param slot the property field number or spill slot |
jlaskey@3 | 220 | * @param getter the property getter |
jlaskey@3 | 221 | * @param setter the property setter or null if non writable, non configurable |
jlaskey@3 | 222 | */ |
attila@963 | 223 | private AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) { |
attila@963 | 224 | super(key, flags | (getter.type().returnType().isPrimitive() ? IS_NASGEN_PRIMITIVE : 0), slot); |
attila@963 | 225 | assert !isSpill(); |
jlaskey@3 | 226 | |
jlaskey@3 | 227 | // we don't need to prep the setters these will never be invalidated as this is a nasgen |
jlaskey@3 | 228 | // or known type getter/setter. No invalidations will take place |
jlaskey@3 | 229 | |
jlaskey@3 | 230 | final Class<?> getterType = getter.type().returnType(); |
jlaskey@3 | 231 | final Class<?> setterType = setter == null ? null : setter.type().parameterType(1); |
jlaskey@3 | 232 | |
jlaskey@3 | 233 | assert setterType == null || setterType == getterType; |
attila@963 | 234 | if (OBJECT_FIELDS_ONLY) { |
attila@963 | 235 | primitiveGetter = primitiveSetter = null; |
attila@963 | 236 | } else { |
attila@963 | 237 | if (getterType == int.class || getterType == long.class) { |
attila@963 | 238 | primitiveGetter = MH.asType(getter, Lookup.GET_PRIMITIVE_TYPE); |
attila@963 | 239 | primitiveSetter = setter == null ? null : MH.asType(setter, Lookup.SET_PRIMITIVE_TYPE); |
attila@963 | 240 | } else if (getterType == double.class) { |
attila@963 | 241 | primitiveGetter = MH.asType(MH.filterReturnValue(getter, ObjectClassGenerator.PACK_DOUBLE), Lookup.GET_PRIMITIVE_TYPE); |
attila@963 | 242 | primitiveSetter = setter == null ? null : MH.asType(MH.filterArguments(setter, 1, ObjectClassGenerator.UNPACK_DOUBLE), Lookup.SET_PRIMITIVE_TYPE); |
attila@963 | 243 | } else { |
attila@963 | 244 | primitiveGetter = primitiveSetter = null; |
jlaskey@3 | 245 | } |
jlaskey@3 | 246 | } |
jlaskey@3 | 247 | |
attila@963 | 248 | assert primitiveGetter == null || primitiveGetter.type() == Lookup.GET_PRIMITIVE_TYPE : primitiveGetter + "!=" + Lookup.GET_PRIMITIVE_TYPE; |
attila@963 | 249 | assert primitiveSetter == null || primitiveSetter.type() == Lookup.SET_PRIMITIVE_TYPE : primitiveSetter; |
jlaskey@3 | 250 | |
attila@963 | 251 | objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter; |
attila@963 | 252 | objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter; |
attila@224 | 253 | |
attila@963 | 254 | setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : getterType); |
attila@224 | 255 | } |
attila@224 | 256 | |
jlaskey@3 | 257 | /** |
attila@963 | 258 | * Normal ACCESS PROPERTY constructor given a structure class. |
jlaskey@80 | 259 | * Constructor for dual field AccessorPropertys. |
jlaskey@80 | 260 | * |
jlaskey@80 | 261 | * @param key property key |
jlaskey@80 | 262 | * @param flags property flags |
jlaskey@80 | 263 | * @param structure structure for objects associated with this property |
jlaskey@80 | 264 | * @param slot property field number or spill slot |
jlaskey@80 | 265 | */ |
jlaskey@80 | 266 | public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) { |
jlaskey@80 | 267 | super(key, flags, slot); |
jlaskey@80 | 268 | |
hannesw@828 | 269 | initGetterSetter(structure); |
attila@963 | 270 | initializeType(); |
hannesw@828 | 271 | } |
hannesw@828 | 272 | |
hannesw@828 | 273 | private void initGetterSetter(final Class<?> structure) { |
hannesw@828 | 274 | final int slot = getSlot(); |
jlaskey@80 | 275 | /* |
jlaskey@80 | 276 | * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also |
jlaskey@80 | 277 | * works in dual field mode, it only means that the property never has a primitive |
jlaskey@80 | 278 | * representation. |
jlaskey@80 | 279 | */ |
jlaskey@80 | 280 | |
jlaskey@80 | 281 | if (isParameter() && hasArguments()) { |
attila@963 | 282 | //parameters are always stored in an object array, which may or may not be a good idea |
attila@963 | 283 | final MethodHandle arguments = MH.getter(LOOKUP, structure, "arguments", ScriptObject.class); |
attila@422 | 284 | objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE); |
attila@422 | 285 | objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE); |
attila@963 | 286 | primitiveGetter = null; |
attila@963 | 287 | primitiveSetter = null; |
jlaskey@80 | 288 | } else { |
attila@963 | 289 | final Accessors gs = GETTERS_SETTERS.get(structure); |
attila@963 | 290 | objectGetter = gs.objectGetters[slot]; |
attila@963 | 291 | primitiveGetter = gs.primitiveGetters[slot]; |
attila@963 | 292 | objectSetter = gs.objectSetters[slot]; |
attila@963 | 293 | primitiveSetter = gs.primitiveSetters[slot]; |
jlaskey@80 | 294 | } |
jlaskey@80 | 295 | } |
jlaskey@80 | 296 | |
jlaskey@80 | 297 | /** |
attila@963 | 298 | * Constructor |
jlaskey@3 | 299 | * |
attila@963 | 300 | * @param key key |
attila@963 | 301 | * @param flags flags |
attila@963 | 302 | * @param slot field slot index |
attila@963 | 303 | * @param owner owner of property |
attila@963 | 304 | * @param initialValue initial value to which the property can be set |
jlaskey@3 | 305 | */ |
attila@963 | 306 | protected AccessorProperty(final String key, final int flags, final int slot, final ScriptObject owner, final Object initialValue) { |
attila@963 | 307 | this(key, flags, owner.getClass(), slot); |
attila@963 | 308 | setInitialValue(owner, initialValue); |
attila@963 | 309 | } |
jlaskey@3 | 310 | |
attila@963 | 311 | /** |
attila@963 | 312 | * Normal access property constructor that overrides the type |
attila@963 | 313 | * Override the initial type. Used for Object Literals |
attila@963 | 314 | * |
attila@963 | 315 | * @param key key |
attila@963 | 316 | * @param flags flags |
attila@963 | 317 | * @param structure structure to JO subclass |
attila@963 | 318 | * @param slot field slot index |
attila@963 | 319 | * @param initialType initial type of the property |
attila@963 | 320 | */ |
attila@963 | 321 | public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType) { |
attila@963 | 322 | this(key, flags, structure, slot); |
attila@963 | 323 | setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : initialType); |
attila@963 | 324 | } |
attila@963 | 325 | |
attila@963 | 326 | /** |
attila@963 | 327 | * Copy constructor that may change type and in that case clear the cache. Important to do that before |
attila@963 | 328 | * type change or getters will be created already stale. |
attila@963 | 329 | * |
attila@963 | 330 | * @param property property |
attila@963 | 331 | * @param newType new type |
attila@963 | 332 | */ |
attila@963 | 333 | protected AccessorProperty(final AccessorProperty property, final Class<?> newType) { |
attila@963 | 334 | super(property, property.getFlags()); |
attila@963 | 335 | |
attila@963 | 336 | this.GETTER_CACHE = newType != property.getCurrentType() ? new MethodHandle[NOOF_TYPES] : property.GETTER_CACHE; |
jlaskey@3 | 337 | this.primitiveGetter = property.primitiveGetter; |
jlaskey@3 | 338 | this.primitiveSetter = property.primitiveSetter; |
jlaskey@3 | 339 | this.objectGetter = property.objectGetter; |
jlaskey@3 | 340 | this.objectSetter = property.objectSetter; |
jlaskey@3 | 341 | |
attila@963 | 342 | setCurrentType(newType); |
attila@963 | 343 | } |
attila@963 | 344 | |
attila@963 | 345 | /** |
attila@963 | 346 | * COPY constructor |
attila@963 | 347 | * |
attila@963 | 348 | * @param property source property |
attila@963 | 349 | */ |
attila@963 | 350 | protected AccessorProperty(final AccessorProperty property) { |
attila@963 | 351 | this(property, property.getCurrentType()); |
attila@963 | 352 | } |
attila@963 | 353 | |
attila@963 | 354 | /** |
attila@963 | 355 | * Set initial value of a script object's property |
attila@963 | 356 | * @param owner owner |
attila@963 | 357 | * @param initialValue initial value |
attila@963 | 358 | */ |
attila@963 | 359 | protected final void setInitialValue(final ScriptObject owner, final Object initialValue) { |
attila@963 | 360 | setCurrentType(JSType.unboxedFieldType(initialValue)); |
attila@963 | 361 | if (initialValue instanceof Integer) { |
attila@963 | 362 | invokeSetter(owner, ((Integer)initialValue).intValue()); |
attila@963 | 363 | } else if (initialValue instanceof Long) { |
attila@963 | 364 | invokeSetter(owner, ((Long)initialValue).longValue()); |
attila@963 | 365 | } else if (initialValue instanceof Double) { |
attila@963 | 366 | invokeSetter(owner, ((Double)initialValue).doubleValue()); |
attila@963 | 367 | } else { |
attila@963 | 368 | invokeSetter(owner, initialValue); |
attila@963 | 369 | } |
attila@963 | 370 | } |
attila@963 | 371 | |
attila@963 | 372 | /** |
attila@963 | 373 | * Initialize the type of a property |
attila@963 | 374 | */ |
attila@963 | 375 | protected final void initializeType() { |
attila@963 | 376 | setCurrentType(OBJECT_FIELDS_ONLY ? Object.class : null); |
jlaskey@3 | 377 | } |
jlaskey@3 | 378 | |
hannesw@828 | 379 | private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException { |
hannesw@828 | 380 | s.defaultReadObject(); |
hannesw@828 | 381 | // Restore getters array |
attila@963 | 382 | GETTER_CACHE = new MethodHandle[NOOF_TYPES]; |
hannesw@828 | 383 | } |
hannesw@828 | 384 | |
jlaskey@3 | 385 | private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) { |
jlaskey@3 | 386 | if (mh == null) { |
jlaskey@3 | 387 | return null; |
jlaskey@3 | 388 | } |
jlaskey@3 | 389 | |
jlaskey@3 | 390 | return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class); |
jlaskey@3 | 391 | } |
jlaskey@3 | 392 | |
jlaskey@3 | 393 | @Override |
attila@963 | 394 | public Property copy() { |
jlaskey@3 | 395 | return new AccessorProperty(this); |
jlaskey@3 | 396 | } |
jlaskey@3 | 397 | |
jlaskey@3 | 398 | @Override |
attila@963 | 399 | public Property copy(final Class<?> newType) { |
attila@963 | 400 | return new AccessorProperty(this, newType); |
hannesw@291 | 401 | } |
hannesw@291 | 402 | |
hannesw@291 | 403 | @Override |
attila@963 | 404 | public int getIntValue(final ScriptObject self, final ScriptObject owner) { |
attila@963 | 405 | try { |
attila@963 | 406 | return (int)getGetter(int.class).invokeExact((Object)self); |
attila@963 | 407 | } catch (final Error | RuntimeException e) { |
attila@963 | 408 | throw e; |
attila@963 | 409 | } catch (final Throwable e) { |
attila@963 | 410 | throw new RuntimeException(e); |
lagergren@295 | 411 | } |
attila@963 | 412 | } |
lagergren@295 | 413 | |
attila@963 | 414 | @Override |
attila@963 | 415 | public long getLongValue(final ScriptObject self, final ScriptObject owner) { |
lagergren@295 | 416 | try { |
attila@963 | 417 | return (long)getGetter(long.class).invokeExact((Object)self); |
attila@963 | 418 | } catch (final Error | RuntimeException e) { |
lagergren@295 | 419 | throw e; |
lagergren@295 | 420 | } catch (final Throwable e) { |
lagergren@295 | 421 | throw new RuntimeException(e); |
hannesw@291 | 422 | } |
hannesw@291 | 423 | } |
hannesw@291 | 424 | |
attila@963 | 425 | @Override |
attila@963 | 426 | public double getDoubleValue(final ScriptObject self, final ScriptObject owner) { |
attila@963 | 427 | try { |
attila@963 | 428 | return (double)getGetter(double.class).invokeExact((Object)self); |
attila@963 | 429 | } catch (final Error | RuntimeException e) { |
attila@963 | 430 | throw e; |
attila@963 | 431 | } catch (final Throwable e) { |
attila@963 | 432 | throw new RuntimeException(e); |
hannesw@291 | 433 | } |
hannesw@415 | 434 | } |
hannesw@415 | 435 | |
attila@963 | 436 | @Override |
attila@963 | 437 | public Object getObjectValue(final ScriptObject self, final ScriptObject owner) { |
attila@963 | 438 | try { |
attila@963 | 439 | return getGetter(Object.class).invokeExact((Object)self); |
attila@963 | 440 | } catch (final Error | RuntimeException e) { |
attila@963 | 441 | throw e; |
attila@963 | 442 | } catch (final Throwable e) { |
attila@963 | 443 | throw new RuntimeException(e); |
hannesw@415 | 444 | } |
attila@963 | 445 | } |
attila@963 | 446 | |
attila@963 | 447 | /** |
attila@963 | 448 | * Invoke setter for this property with a value |
attila@963 | 449 | * @param self owner |
attila@963 | 450 | * @param value value |
attila@963 | 451 | */ |
attila@963 | 452 | protected final void invokeSetter(final ScriptObject self, final int value) { |
attila@963 | 453 | try { |
attila@963 | 454 | getSetter(int.class, self.getMap()).invokeExact((Object)self, value); |
attila@963 | 455 | } catch (final Error | RuntimeException e) { |
attila@963 | 456 | throw e; |
attila@963 | 457 | } catch (final Throwable e) { |
attila@963 | 458 | throw new RuntimeException(e); |
attila@963 | 459 | } |
attila@963 | 460 | } |
attila@963 | 461 | |
attila@963 | 462 | /** |
attila@963 | 463 | * Invoke setter for this property with a value |
attila@963 | 464 | * @param self owner |
attila@963 | 465 | * @param value value |
attila@963 | 466 | */ |
attila@963 | 467 | protected final void invokeSetter(final ScriptObject self, final long value) { |
attila@963 | 468 | try { |
attila@963 | 469 | getSetter(long.class, self.getMap()).invokeExact((Object)self, value); |
attila@963 | 470 | } catch (final Error | RuntimeException e) { |
attila@963 | 471 | throw e; |
attila@963 | 472 | } catch (final Throwable e) { |
attila@963 | 473 | throw new RuntimeException(e); |
attila@963 | 474 | } |
attila@963 | 475 | } |
attila@963 | 476 | |
attila@963 | 477 | /** |
attila@963 | 478 | * Invoke setter for this property with a value |
attila@963 | 479 | * @param self owner |
attila@963 | 480 | * @param value value |
attila@963 | 481 | */ |
attila@963 | 482 | protected final void invokeSetter(final ScriptObject self, final double value) { |
attila@963 | 483 | try { |
attila@963 | 484 | getSetter(double.class, self.getMap()).invokeExact((Object)self, value); |
attila@963 | 485 | } catch (final Error | RuntimeException e) { |
attila@963 | 486 | throw e; |
attila@963 | 487 | } catch (final Throwable e) { |
attila@963 | 488 | throw new RuntimeException(e); |
attila@963 | 489 | } |
attila@963 | 490 | } |
attila@963 | 491 | |
attila@963 | 492 | /** |
attila@963 | 493 | * Invoke setter for this property with a value |
attila@963 | 494 | * @param self owner |
attila@963 | 495 | * @param value value |
attila@963 | 496 | */ |
attila@963 | 497 | protected final void invokeSetter(final ScriptObject self, final Object value) { |
attila@963 | 498 | try { |
attila@963 | 499 | getSetter(Object.class, self.getMap()).invokeExact((Object)self, value); |
attila@963 | 500 | } catch (final Error | RuntimeException e) { |
attila@963 | 501 | throw e; |
attila@963 | 502 | } catch (final Throwable e) { |
attila@963 | 503 | throw new RuntimeException(e); |
attila@963 | 504 | } |
attila@963 | 505 | } |
attila@963 | 506 | |
attila@963 | 507 | @Override |
attila@963 | 508 | public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict) { |
attila@963 | 509 | assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; |
attila@963 | 510 | invokeSetter(self, value); |
attila@963 | 511 | } |
attila@963 | 512 | |
attila@963 | 513 | @Override |
attila@963 | 514 | public void setValue(final ScriptObject self, final ScriptObject owner, final long value, final boolean strict) { |
attila@963 | 515 | assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; |
attila@963 | 516 | invokeSetter(self, value); |
attila@963 | 517 | } |
attila@963 | 518 | |
attila@963 | 519 | @Override |
attila@963 | 520 | public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict) { |
attila@963 | 521 | assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable"; |
attila@963 | 522 | invokeSetter(self, value); |
attila@963 | 523 | } |
attila@963 | 524 | |
attila@963 | 525 | @Override |
attila@963 | 526 | public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict) { |
attila@963 | 527 | //this is sometimes used for bootstrapping, hence no assert. ugly. |
attila@963 | 528 | invokeSetter(self, value); |
hannesw@415 | 529 | } |
hannesw@415 | 530 | |
hannesw@415 | 531 | @Override |
hannesw@828 | 532 | void initMethodHandles(final Class<?> structure) { |
attila@963 | 533 | // sanity check for structure class |
hannesw@828 | 534 | if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) { |
hannesw@828 | 535 | throw new IllegalArgumentException(); |
hannesw@828 | 536 | } |
attila@963 | 537 | // this method is overridden in SpillProperty |
attila@963 | 538 | assert !isSpill(); |
attila@963 | 539 | initGetterSetter(structure); |
hannesw@828 | 540 | } |
hannesw@828 | 541 | |
hannesw@828 | 542 | @Override |
hannesw@415 | 543 | public MethodHandle getGetter(final Class<?> type) { |
jlaskey@3 | 544 | final int i = getAccessorTypeIndex(type); |
hannesw@415 | 545 | |
attila@963 | 546 | assert type == int.class || |
attila@963 | 547 | type == long.class || |
attila@963 | 548 | type == double.class || |
attila@963 | 549 | type == Object.class : |
attila@963 | 550 | "invalid getter type " + type + " for " + getKey(); |
attila@963 | 551 | |
hannesw@991 | 552 | checkUndeclared(); |
hannesw@991 | 553 | |
attila@963 | 554 | //all this does is add a return value filter for object fields only |
attila@963 | 555 | final MethodHandle[] getterCache = GETTER_CACHE; |
attila@963 | 556 | final MethodHandle cachedGetter = getterCache[i]; |
attila@963 | 557 | final MethodHandle getter; |
attila@963 | 558 | if (cachedGetter != null) { |
attila@963 | 559 | getter = cachedGetter; |
attila@963 | 560 | } else { |
attila@963 | 561 | getter = debug( |
attila@963 | 562 | createGetter( |
attila@963 | 563 | getCurrentType(), |
attila@963 | 564 | type, |
attila@963 | 565 | primitiveGetter, |
attila@963 | 566 | objectGetter, |
attila@963 | 567 | INVALID_PROGRAM_POINT), |
attila@963 | 568 | getCurrentType(), |
attila@963 | 569 | type, |
attila@963 | 570 | "get"); |
attila@963 | 571 | getterCache[i] = getter; |
attila@963 | 572 | } |
attila@963 | 573 | assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class; |
attila@963 | 574 | return getter; |
attila@963 | 575 | } |
attila@963 | 576 | |
attila@963 | 577 | @Override |
attila@963 | 578 | public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) { |
attila@963 | 579 | // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type |
attila@963 | 580 | if (objectGetter == null) { |
attila@963 | 581 | return getOptimisticPrimitiveGetter(type, programPoint); |
jlaskey@3 | 582 | } |
jlaskey@3 | 583 | |
hannesw@991 | 584 | checkUndeclared(); |
hannesw@991 | 585 | |
attila@963 | 586 | return debug( |
attila@963 | 587 | createGetter( |
attila@963 | 588 | getCurrentType(), |
attila@963 | 589 | type, |
attila@963 | 590 | primitiveGetter, |
attila@963 | 591 | objectGetter, |
attila@963 | 592 | programPoint), |
attila@963 | 593 | getCurrentType(), |
attila@963 | 594 | type, |
attila@963 | 595 | "get"); |
attila@963 | 596 | } |
attila@963 | 597 | |
attila@963 | 598 | private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) { |
attila@963 | 599 | final MethodHandle g = getGetter(getCurrentType()); |
attila@963 | 600 | return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type)); |
jlaskey@3 | 601 | } |
jlaskey@3 | 602 | |
jlaskey@3 | 603 | private Property getWiderProperty(final Class<?> type) { |
attila@963 | 604 | return copy(type); //invalidate cache of new property |
attila@963 | 605 | |
jlaskey@3 | 606 | } |
jlaskey@3 | 607 | |
jlaskey@3 | 608 | private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) { |
jlaskey@3 | 609 | final PropertyMap newMap = oldMap.replaceProperty(this, newProperty); |
jlaskey@3 | 610 | assert oldMap.size() > 0; |
jlaskey@3 | 611 | assert newMap.size() == oldMap.size(); |
jlaskey@3 | 612 | return newMap; |
jlaskey@3 | 613 | } |
jlaskey@3 | 614 | |
hannesw@991 | 615 | private void checkUndeclared() { |
hannesw@991 | 616 | if ((getFlags() & NEEDS_DECLARATION) != 0) { |
hannesw@991 | 617 | // a lexically defined variable that hasn't seen its declaration - throw ReferenceError |
hannesw@991 | 618 | throw ECMAErrors.referenceError("not.defined", getKey()); |
hannesw@991 | 619 | } |
hannesw@991 | 620 | } |
hannesw@991 | 621 | |
jlaskey@3 | 622 | // the final three arguments are for debug printout purposes only |
jlaskey@3 | 623 | @SuppressWarnings("unused") |
attila@963 | 624 | private static Object replaceMap(final Object sobj, final PropertyMap newMap) { |
jlaskey@3 | 625 | ((ScriptObject)sobj).setMap(newMap); |
jlaskey@3 | 626 | return sobj; |
jlaskey@3 | 627 | } |
jlaskey@3 | 628 | |
attila@963 | 629 | @SuppressWarnings("unused") |
attila@963 | 630 | private static Object invalidateSwitchPoint(final Object obj, final SwitchPoint sp) { |
attila@963 | 631 | SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); |
attila@963 | 632 | return obj; |
attila@963 | 633 | } |
attila@963 | 634 | |
jlaskey@3 | 635 | private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) { |
attila@963 | 636 | return debug(createSetter(forType, type, primitiveSetter, objectSetter), getCurrentType(), type, "set"); |
attila@963 | 637 | } |
attila@963 | 638 | |
attila@963 | 639 | /** |
attila@963 | 640 | * Is this property of the undefined type? |
attila@963 | 641 | * @return true if undefined |
attila@963 | 642 | */ |
attila@963 | 643 | protected final boolean isUndefined() { |
attila@963 | 644 | return getCurrentType() == null; |
jlaskey@3 | 645 | } |
jlaskey@3 | 646 | |
jlaskey@3 | 647 | @Override |
jlaskey@3 | 648 | public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) { |
hannesw@991 | 649 | checkUndeclared(); |
hannesw@991 | 650 | |
hannesw@991 | 651 | final int typeIndex = getAccessorTypeIndex(type); |
hannesw@991 | 652 | final int currentTypeIndex = getAccessorTypeIndex(getCurrentType()); |
jlaskey@3 | 653 | |
jlaskey@3 | 654 | //if we are asking for an object setter, but are still a primitive type, we might try to box it |
jlaskey@242 | 655 | MethodHandle mh; |
hannesw@991 | 656 | if (needsInvalidator(typeIndex, currentTypeIndex)) { |
jlaskey@3 | 657 | final Property newProperty = getWiderProperty(type); |
jlaskey@3 | 658 | final PropertyMap newMap = getWiderMap(currentMap, newProperty); |
attila@963 | 659 | |
jlaskey@3 | 660 | final MethodHandle widerSetter = newProperty.getSetter(type, newMap); |
attila@963 | 661 | final Class<?> ct = getCurrentType(); |
attila@963 | 662 | mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap)); |
attila@963 | 663 | if (ct != null && ct.isPrimitive() && !type.isPrimitive()) { |
attila@963 | 664 | mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh); |
jlaskey@3 | 665 | } |
jlaskey@242 | 666 | } else { |
hannesw@991 | 667 | final Class<?> forType = isUndefined() ? type : getCurrentType(); |
attila@963 | 668 | mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type); |
jlaskey@3 | 669 | } |
jlaskey@3 | 670 | |
attila@963 | 671 | /** |
attila@963 | 672 | * Check if this is a special global name that requires switchpoint invalidation |
attila@963 | 673 | */ |
attila@963 | 674 | final SwitchPoint ccb = getChangeCallback(); |
attila@963 | 675 | if (ccb != null && ccb != NO_CHANGE_CALLBACK) { |
attila@963 | 676 | mh = MH.filterArguments(mh, 0, MH.insertArguments(debugInvalidate(getKey(), ccb), 1, changeCallback)); |
attila@963 | 677 | } |
attila@963 | 678 | |
attila@963 | 679 | assert mh.type().returnType() == void.class : mh.type(); |
attila@963 | 680 | |
hannesw@291 | 681 | return mh; |
jlaskey@3 | 682 | } |
jlaskey@3 | 683 | |
attila@963 | 684 | /** |
attila@963 | 685 | * Get the change callback for this property |
attila@963 | 686 | * @return switchpoint that is invalidated when property changes |
attila@963 | 687 | */ |
attila@963 | 688 | protected SwitchPoint getChangeCallback() { |
attila@963 | 689 | if (changeCallback == null) { |
attila@963 | 690 | try { |
attila@963 | 691 | changeCallback = Global.instance().getChangeCallback(getKey()); |
attila@963 | 692 | } catch (final NullPointerException e) { |
attila@963 | 693 | assert !"apply".equals(getKey()) && !"call".equals(getKey()); |
attila@963 | 694 | //empty |
attila@963 | 695 | } |
attila@963 | 696 | if (changeCallback == null) { |
attila@963 | 697 | changeCallback = NO_CHANGE_CALLBACK; |
attila@963 | 698 | } |
attila@963 | 699 | } |
attila@963 | 700 | return changeCallback; |
attila@963 | 701 | } |
attila@963 | 702 | |
jlaskey@3 | 703 | @Override |
attila@963 | 704 | public final boolean canChangeType() { |
jlaskey@3 | 705 | if (OBJECT_FIELDS_ONLY) { |
jlaskey@3 | 706 | return false; |
jlaskey@3 | 707 | } |
hannesw@991 | 708 | // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST. |
hannesw@991 | 709 | return getCurrentType() == null || (getCurrentType() != Object.class && (isConfigurable() || isWritable())); |
jlaskey@3 | 710 | } |
jlaskey@3 | 711 | |
hannesw@991 | 712 | private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) { |
hannesw@991 | 713 | return canChangeType() && typeIndex > currentTypeIndex; |
jlaskey@3 | 714 | } |
jlaskey@3 | 715 | |
attila@963 | 716 | @Override |
attila@963 | 717 | public final void setCurrentType(final Class<?> currentType) { |
attila@963 | 718 | assert currentType != boolean.class : "no boolean storage support yet - fix this"; |
attila@963 | 719 | this.currentType = currentType == null ? null : currentType.isPrimitive() ? currentType : Object.class; |
jlaskey@3 | 720 | } |
jlaskey@3 | 721 | |
jlaskey@3 | 722 | @Override |
jlaskey@3 | 723 | public Class<?> getCurrentType() { |
jlaskey@3 | 724 | return currentType; |
jlaskey@3 | 725 | } |
jlaskey@3 | 726 | |
attila@963 | 727 | |
attila@963 | 728 | private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) { |
attila@963 | 729 | if (!Context.DEBUG || !Global.hasInstance()) { |
attila@963 | 730 | return mh; |
attila@963 | 731 | } |
attila@963 | 732 | |
attila@963 | 733 | final Context context = Context.getContextTrusted(); |
attila@963 | 734 | assert context != null; |
attila@963 | 735 | |
attila@963 | 736 | return context.addLoggingToHandle( |
attila@963 | 737 | ObjectClassGenerator.class, |
attila@963 | 738 | Level.INFO, |
attila@963 | 739 | mh, |
attila@963 | 740 | 0, |
attila@963 | 741 | true, |
attila@963 | 742 | new Supplier<String>() { |
attila@963 | 743 | @Override |
attila@963 | 744 | public String get() { |
attila@963 | 745 | return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')'; |
attila@963 | 746 | } |
attila@963 | 747 | }); |
jlaskey@3 | 748 | } |
jlaskey@3 | 749 | |
attila@963 | 750 | private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) { |
attila@963 | 751 | if (!Context.DEBUG || !Global.hasInstance()) { |
attila@963 | 752 | return REPLACE_MAP; |
attila@963 | 753 | } |
attila@963 | 754 | |
attila@963 | 755 | final Context context = Context.getContextTrusted(); |
attila@963 | 756 | assert context != null; |
attila@963 | 757 | |
attila@963 | 758 | MethodHandle mh = context.addLoggingToHandle( |
attila@963 | 759 | ObjectClassGenerator.class, |
attila@963 | 760 | REPLACE_MAP, |
attila@963 | 761 | new Supplier<String>() { |
attila@963 | 762 | @Override |
attila@963 | 763 | public String get() { |
attila@963 | 764 | return "Type change for '" + getKey() + "' " + oldType + "=>" + newType; |
attila@963 | 765 | } |
attila@963 | 766 | }); |
attila@963 | 767 | |
attila@963 | 768 | mh = context.addLoggingToHandle( |
attila@963 | 769 | ObjectClassGenerator.class, |
attila@963 | 770 | Level.FINEST, |
attila@963 | 771 | mh, |
attila@963 | 772 | Integer.MAX_VALUE, |
attila@963 | 773 | false, |
attila@963 | 774 | new Supplier<String>() { |
attila@963 | 775 | @Override |
attila@963 | 776 | public String get() { |
attila@963 | 777 | return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap; |
attila@963 | 778 | } |
attila@963 | 779 | }); |
attila@963 | 780 | return mh; |
attila@963 | 781 | } |
attila@963 | 782 | |
attila@963 | 783 | private static MethodHandle debugInvalidate(final String key, final SwitchPoint sp) { |
attila@963 | 784 | if (!Context.DEBUG || !Global.hasInstance()) { |
attila@963 | 785 | return INVALIDATE_SP; |
attila@963 | 786 | } |
attila@963 | 787 | |
attila@963 | 788 | final Context context = Context.getContextTrusted(); |
attila@963 | 789 | assert context != null; |
attila@963 | 790 | |
attila@963 | 791 | return context.addLoggingToHandle( |
attila@963 | 792 | ObjectClassGenerator.class, |
attila@963 | 793 | INVALIDATE_SP, |
attila@963 | 794 | new Supplier<String>() { |
attila@963 | 795 | @Override |
attila@963 | 796 | public String get() { |
attila@963 | 797 | return "Field change callback for " + key + " triggered: " + sp; |
attila@963 | 798 | } |
attila@963 | 799 | }); |
attila@963 | 800 | } |
attila@963 | 801 | |
attila@963 | 802 | private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) { |
attila@963 | 803 | return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types)); |
attila@963 | 804 | } |
jlaskey@3 | 805 | } |