AccessorProperty.java revision 1571:fd97b9047199
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
26package jdk.nashorn.internal.runtime;
27
28import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE;
29import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter;
30import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter;
31import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldCount;
32import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName;
33import static jdk.nashorn.internal.lookup.Lookup.MH;
34import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName;
35import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
36import static jdk.nashorn.internal.runtime.JSType.getNumberOfAccessorTypes;
37import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
38import java.io.IOException;
39import java.io.ObjectInputStream;
40import java.lang.invoke.MethodHandle;
41import java.lang.invoke.MethodHandles;
42import java.lang.invoke.SwitchPoint;
43import java.util.function.Supplier;
44import java.util.logging.Level;
45import jdk.nashorn.internal.codegen.ObjectClassGenerator;
46import jdk.nashorn.internal.codegen.types.Type;
47import jdk.nashorn.internal.lookup.Lookup;
48import 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 */
54public 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 Object 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 Object 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) {
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 Object 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 Object 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 Object 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 double getDoubleValue(final ScriptObject self, final ScriptObject owner) {
405        try {
406            return (double)getGetter(double.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 Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
416        try {
417            return getGetter(Object.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     /**
426      * Invoke setter for this property with a value
427      * @param self  owner
428      * @param value value
429      */
430    protected final void invokeSetter(final ScriptObject self, final int value) {
431        try {
432            getSetter(int.class, self.getMap()).invokeExact((Object)self, value);
433        } catch (final Error | RuntimeException e) {
434            throw e;
435        } catch (final Throwable e) {
436            throw new RuntimeException(e);
437        }
438    }
439
440    /**
441     * Invoke setter for this property with a value
442     * @param self  owner
443     * @param value value
444     */
445    protected final void invokeSetter(final ScriptObject self, final double value) {
446        try {
447            getSetter(double.class, self.getMap()).invokeExact((Object)self, value);
448        } catch (final Error | RuntimeException e) {
449            throw e;
450        } catch (final Throwable e) {
451            throw new RuntimeException(e);
452        }
453    }
454
455    /**
456     * Invoke setter for this property with a value
457     * @param self  owner
458     * @param value value
459     */
460    protected final void invokeSetter(final ScriptObject self, final Object value) {
461        try {
462            getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
463        } catch (final Error | RuntimeException e) {
464            throw e;
465        } catch (final Throwable e) {
466            throw new RuntimeException(e);
467        }
468    }
469
470    @Override
471    public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict)  {
472        assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
473        invokeSetter(self, value);
474    }
475
476    @Override
477    public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict)  {
478        assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
479        invokeSetter(self, value);
480    }
481
482    @Override
483    public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict)  {
484        //this is sometimes used for bootstrapping, hence no assert. ugly.
485        invokeSetter(self, value);
486    }
487
488    @Override
489    void initMethodHandles(final Class<?> structure) {
490        // sanity check for structure class
491        if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) {
492            throw new IllegalArgumentException();
493        }
494        // this method is overridden in SpillProperty
495        assert !isSpill();
496        initGetterSetter(structure);
497    }
498
499    @Override
500    public MethodHandle getGetter(final Class<?> type) {
501        final int i = getAccessorTypeIndex(type);
502
503        assert type == int.class ||
504                type == double.class ||
505                type == Object.class :
506                "invalid getter type " + type + " for " + getKey();
507
508        checkUndeclared();
509
510        //all this does is add a return value filter for object fields only
511        final MethodHandle[] getterCache = GETTER_CACHE;
512        final MethodHandle cachedGetter = getterCache[i];
513        final MethodHandle getter;
514        if (cachedGetter != null) {
515            getter = cachedGetter;
516        } else {
517            getter = debug(
518                createGetter(
519                    getLocalType(),
520                    type,
521                    primitiveGetter,
522                    objectGetter,
523                    INVALID_PROGRAM_POINT),
524                getLocalType(),
525                type,
526                "get");
527            getterCache[i] = getter;
528       }
529       assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class;
530       return getter;
531    }
532
533    @Override
534    public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) {
535        // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type
536        if (objectGetter == null) {
537            return getOptimisticPrimitiveGetter(type, programPoint);
538        }
539
540        checkUndeclared();
541
542        return debug(
543            createGetter(
544                getLocalType(),
545                type,
546                primitiveGetter,
547                objectGetter,
548                programPoint),
549            getLocalType(),
550            type,
551            "get");
552    }
553
554    private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) {
555        final MethodHandle g = getGetter(getLocalType());
556        return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type));
557    }
558
559    private Property getWiderProperty(final Class<?> type) {
560        return copy(type); //invalidate cache of new property
561    }
562
563    private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
564        final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
565        assert oldMap.size() > 0;
566        assert newMap.size() == oldMap.size();
567        return newMap;
568    }
569
570    private void checkUndeclared() {
571        if ((getFlags() & NEEDS_DECLARATION) != 0) {
572            // a lexically defined variable that hasn't seen its declaration - throw ReferenceError
573            throw ECMAErrors.referenceError("not.defined", getKey().toString());
574        }
575    }
576
577    // the final three arguments are for debug printout purposes only
578    @SuppressWarnings("unused")
579    private static Object replaceMap(final Object sobj, final PropertyMap newMap) {
580        ((ScriptObject)sobj).setMap(newMap);
581        return sobj;
582    }
583
584    @SuppressWarnings("unused")
585    private static Object invalidateSwitchPoint(final AccessorProperty property, final Object obj) {
586         if (!property.builtinSwitchPoint.hasBeenInvalidated()) {
587            SwitchPoint.invalidateAll(new SwitchPoint[] { property.builtinSwitchPoint });
588        }
589        return obj;
590    }
591
592    private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
593        return debug(createSetter(forType, type, primitiveSetter, objectSetter), getLocalType(), type, "set");
594    }
595
596    /**
597     * Is this property of the undefined type?
598     * @return true if undefined
599     */
600    protected final boolean isUndefined() {
601        return getLocalType() == null;
602    }
603
604    @Override
605    public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
606        checkUndeclared();
607
608        final int typeIndex        = getAccessorTypeIndex(type);
609        final int currentTypeIndex = getAccessorTypeIndex(getLocalType());
610
611        //if we are asking for an object setter, but are still a primitive type, we might try to box it
612        MethodHandle mh;
613        if (needsInvalidator(typeIndex, currentTypeIndex)) {
614            final Property     newProperty = getWiderProperty(type);
615            final PropertyMap  newMap      = getWiderMap(currentMap, newProperty);
616
617            final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
618            final Class<?>     ct = getLocalType();
619            mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap));
620            if (ct != null && ct.isPrimitive() && !type.isPrimitive()) {
621                 mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh);
622            }
623        } else {
624            final Class<?> forType = isUndefined() ? type : getLocalType();
625            mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type);
626        }
627
628        if (isBuiltin()) {
629           mh = MH.filterArguments(mh, 0, debugInvalidate(MH.insertArguments(INVALIDATE_SP, 0, this), getKey().toString()));
630        }
631
632        assert mh.type().returnType() == void.class : mh.type();
633
634        return mh;
635    }
636
637    @Override
638    public final boolean canChangeType() {
639        if (!hasDualFields()) {
640            return false;
641        }
642        // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST.
643        return getLocalType() == null || (getLocalType() != Object.class && (isConfigurable() || isWritable()));
644    }
645
646    private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) {
647        return canChangeType() && typeIndex > currentTypeIndex;
648    }
649
650    private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
651        if (!Context.DEBUG || !Global.hasInstance()) {
652            return mh;
653        }
654
655        final Context context = Context.getContextTrusted();
656        assert context != null;
657
658        return context.addLoggingToHandle(
659                ObjectClassGenerator.class,
660                Level.INFO,
661                mh,
662                0,
663                true,
664                new Supplier<String>() {
665                    @Override
666                    public String get() {
667                        return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')';
668                    }
669                });
670    }
671
672    private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) {
673        if (!Context.DEBUG || !Global.hasInstance()) {
674            return REPLACE_MAP;
675        }
676
677        final Context context = Context.getContextTrusted();
678        assert context != null;
679
680        MethodHandle mh = context.addLoggingToHandle(
681                ObjectClassGenerator.class,
682                REPLACE_MAP,
683                new Supplier<String>() {
684                    @Override
685                    public String get() {
686                        return "Type change for '" + getKey() + "' " + oldType + "=>" + newType;
687                    }
688                });
689
690        mh = context.addLoggingToHandle(
691                ObjectClassGenerator.class,
692                Level.FINEST,
693                mh,
694                Integer.MAX_VALUE,
695                false,
696                new Supplier<String>() {
697                    @Override
698                    public String get() {
699                        return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap;
700                    }
701                });
702        return mh;
703    }
704
705    private static MethodHandle debugInvalidate(final MethodHandle invalidator, final String key) {
706        if (!Context.DEBUG || !Global.hasInstance()) {
707            return invalidator;
708        }
709
710        final Context context = Context.getContextTrusted();
711        assert context != null;
712
713        return context.addLoggingToHandle(
714                ObjectClassGenerator.class,
715                invalidator,
716                new Supplier<String>() {
717                    @Override
718                    public String get() {
719                        return "Field change callback for " + key + " triggered ";
720                    }
721                });
722    }
723
724    private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
725        return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types));
726    }
727}
728