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