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