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          */
Accessors(final Class<?> structure)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      */
create(final Object key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter)130     public static AccessorProperty create(final Object 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      */
AccessorProperty(final AccessorProperty property, final Object delegate)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      */
AccessorProperty( final Object key, final int flags, final int slot, final MethodHandle primitiveGetter, final MethodHandle primitiveSetter, final MethodHandle objectGetter, final MethodHandle objectSetter)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      */
AccessorProperty(final Object key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter)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      */
AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot)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 
initGetterSetter(final Class<?> structure)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      */
AccessorProperty(final Object key, final int flags, final int slot, final ScriptObject owner, final Object initialValue)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      */
AccessorProperty(final Object key, final int flags, final Class<?> structure, final int slot, final Class<?> initialType)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      */
AccessorProperty(final AccessorProperty property, final Class<?> newType)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      */
AccessorProperty(final AccessorProperty property)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      */
setInitialValue(final ScriptObject owner, final Object initialValue)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 Double) {
353             invokeSetter(owner, ((Double)initialValue).doubleValue());
354         } else {
355             invokeSetter(owner, initialValue);
356         }
357     }
358 
359     /**
360      * Initialize the type of a property
361      */
initializeType()362     protected final void initializeType() {
363         setType(!hasDualFields() ? Object.class : null);
364     }
365 
readObject(final ObjectInputStream s)366     private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
367         s.defaultReadObject();
368         // Restore getters array
369         GETTER_CACHE = new MethodHandle[NOOF_TYPES];
370     }
371 
bindTo(final MethodHandle mh, final Object receiver)372     private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
373         if (mh == null) {
374             return null;
375         }
376 
377         return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class);
378     }
379 
380     @Override
copy()381     public Property copy() {
382         return new AccessorProperty(this);
383     }
384 
385     @Override
copy(final Class<?> newType)386     public Property copy(final Class<?> newType) {
387         return new AccessorProperty(this, newType);
388     }
389 
390     @Override
getIntValue(final ScriptObject self, final ScriptObject owner)391     public int getIntValue(final ScriptObject self, final ScriptObject owner) {
392         try {
393             return (int)getGetter(int.class).invokeExact((Object)self);
394         } catch (final Error | RuntimeException e) {
395             throw e;
396         } catch (final Throwable e) {
397             throw new RuntimeException(e);
398         }
399      }
400 
401      @Override
getDoubleValue(final ScriptObject self, final ScriptObject owner)402      public double getDoubleValue(final ScriptObject self, final ScriptObject owner) {
403         try {
404             return (double)getGetter(double.class).invokeExact((Object)self);
405         } catch (final Error | RuntimeException e) {
406             throw e;
407         } catch (final Throwable e) {
408             throw new RuntimeException(e);
409         }
410     }
411 
412      @Override
getObjectValue(final ScriptObject self, final ScriptObject owner)413      public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
414         try {
415             return getGetter(Object.class).invokeExact((Object)self);
416         } catch (final Error | RuntimeException e) {
417             throw e;
418         } catch (final Throwable e) {
419             throw new RuntimeException(e);
420         }
421     }
422 
423      /**
424       * Invoke setter for this property with a value
425       * @param self  owner
426       * @param value value
427       */
invokeSetter(final ScriptObject self, final int value)428     protected final void invokeSetter(final ScriptObject self, final int value) {
429         try {
430             getSetter(int.class, self.getMap()).invokeExact((Object)self, value);
431         } catch (final Error | RuntimeException e) {
432             throw e;
433         } catch (final Throwable e) {
434             throw new RuntimeException(e);
435         }
436     }
437 
438     /**
439      * Invoke setter for this property with a value
440      * @param self  owner
441      * @param value value
442      */
invokeSetter(final ScriptObject self, final double value)443     protected final void invokeSetter(final ScriptObject self, final double value) {
444         try {
445             getSetter(double.class, self.getMap()).invokeExact((Object)self, value);
446         } catch (final Error | RuntimeException e) {
447             throw e;
448         } catch (final Throwable e) {
449             throw new RuntimeException(e);
450         }
451     }
452 
453     /**
454      * Invoke setter for this property with a value
455      * @param self  owner
456      * @param value value
457      */
invokeSetter(final ScriptObject self, final Object value)458     protected final void invokeSetter(final ScriptObject self, final Object value) {
459         try {
460             getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
461         } catch (final Error | RuntimeException e) {
462             throw e;
463         } catch (final Throwable e) {
464             throw new RuntimeException(e);
465         }
466     }
467 
468     @Override
setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict)469     public void setValue(final ScriptObject self, final ScriptObject owner, final int value, final boolean strict)  {
470         assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
471         invokeSetter(self, value);
472     }
473 
474     @Override
setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict)475     public void setValue(final ScriptObject self, final ScriptObject owner, final double value, final boolean strict)  {
476         assert isConfigurable() || isWritable() : getKey() + " is not writable or configurable";
477         invokeSetter(self, value);
478     }
479 
480     @Override
setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict)481     public void setValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict)  {
482         //this is sometimes used for bootstrapping, hence no assert. ugly.
483         invokeSetter(self, value);
484     }
485 
486     @Override
initMethodHandles(final Class<?> structure)487     void initMethodHandles(final Class<?> structure) {
488         // sanity check for structure class
489         if (!ScriptObject.class.isAssignableFrom(structure) || !StructureLoader.isStructureClass(structure.getName())) {
490             throw new IllegalArgumentException();
491         }
492         // this method is overridden in SpillProperty
493         assert !isSpill();
494         initGetterSetter(structure);
495     }
496 
497     @Override
getGetter(final Class<?> type)498     public MethodHandle getGetter(final Class<?> type) {
499         final int i = getAccessorTypeIndex(type);
500 
501         assert type == int.class ||
502                 type == double.class ||
503                 type == Object.class :
504                 "invalid getter type " + type + " for " + getKey();
505 
506         checkUndeclared();
507 
508         //all this does is add a return value filter for object fields only
509         final MethodHandle[] getterCache = GETTER_CACHE;
510         final MethodHandle cachedGetter = getterCache[i];
511         final MethodHandle getter;
512         if (cachedGetter != null) {
513             getter = cachedGetter;
514         } else {
515             getter = debug(
516                 createGetter(
517                     getLocalType(),
518                     type,
519                     primitiveGetter,
520                     objectGetter,
521                     INVALID_PROGRAM_POINT),
522                 getLocalType(),
523                 type,
524                 "get");
525             getterCache[i] = getter;
526        }
527        assert getter.type().returnType() == type && getter.type().parameterType(0) == Object.class;
528        return getter;
529     }
530 
531     @Override
getOptimisticGetter(final Class<?> type, final int programPoint)532     public MethodHandle getOptimisticGetter(final Class<?> type, final int programPoint) {
533         // nasgen generated primitive fields like Math.PI have only one known unchangeable primitive type
534         if (objectGetter == null) {
535             return getOptimisticPrimitiveGetter(type, programPoint);
536         }
537 
538         checkUndeclared();
539 
540         return debug(
541             createGetter(
542                 getLocalType(),
543                 type,
544                 primitiveGetter,
545                 objectGetter,
546                 programPoint),
547             getLocalType(),
548             type,
549             "get");
550     }
551 
getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint)552     private MethodHandle getOptimisticPrimitiveGetter(final Class<?> type, final int programPoint) {
553         final MethodHandle g = getGetter(getLocalType());
554         return MH.asType(OptimisticReturnFilters.filterOptimisticReturnValue(g, type, programPoint), g.type().changeReturnType(type));
555     }
556 
getWiderProperty(final Class<?> type)557     private Property getWiderProperty(final Class<?> type) {
558         return copy(type); //invalidate cache of new property
559     }
560 
getWiderMap(final PropertyMap oldMap, final Property newProperty)561     private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
562         final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
563         assert oldMap.size() > 0;
564         assert newMap.size() == oldMap.size();
565         return newMap;
566     }
567 
checkUndeclared()568     private void checkUndeclared() {
569         if ((getFlags() & NEEDS_DECLARATION) != 0) {
570             // a lexically defined variable that hasn't seen its declaration - throw ReferenceError
571             throw ECMAErrors.referenceError("not.defined", getKey().toString());
572         }
573     }
574 
575     // the final three arguments are for debug printout purposes only
576     @SuppressWarnings("unused")
replaceMap(final Object sobj, final PropertyMap newMap)577     private static Object replaceMap(final Object sobj, final PropertyMap newMap) {
578         ((ScriptObject)sobj).setMap(newMap);
579         return sobj;
580     }
581 
582     @SuppressWarnings("unused")
invalidateSwitchPoint(final AccessorProperty property, final Object obj)583     private static Object invalidateSwitchPoint(final AccessorProperty property, final Object obj) {
584          if (!property.builtinSwitchPoint.hasBeenInvalidated()) {
585             SwitchPoint.invalidateAll(new SwitchPoint[] { property.builtinSwitchPoint });
586         }
587         return obj;
588     }
589 
generateSetter(final Class<?> forType, final Class<?> type)590     private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
591         return debug(createSetter(forType, type, primitiveSetter, objectSetter), getLocalType(), type, "set");
592     }
593 
594     /**
595      * Is this property of the undefined type?
596      * @return true if undefined
597      */
isUndefined()598     protected final boolean isUndefined() {
599         return getLocalType() == null;
600     }
601 
602     @Override
hasNativeSetter()603     public boolean hasNativeSetter() {
604         return objectSetter != null;
605     }
606 
607     @Override
getSetter(final Class<?> type, final PropertyMap currentMap)608     public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
609         checkUndeclared();
610 
611         final int typeIndex        = getAccessorTypeIndex(type);
612         final int currentTypeIndex = getAccessorTypeIndex(getLocalType());
613 
614         //if we are asking for an object setter, but are still a primitive type, we might try to box it
615         MethodHandle mh;
616         if (needsInvalidator(typeIndex, currentTypeIndex)) {
617             final Property     newProperty = getWiderProperty(type);
618             final PropertyMap  newMap      = getWiderMap(currentMap, newProperty);
619 
620             final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
621             final Class<?>     ct = getLocalType();
622             mh = MH.filterArguments(widerSetter, 0, MH.insertArguments(debugReplace(ct, type, currentMap, newMap) , 1, newMap));
623             if (ct != null && ct.isPrimitive() && !type.isPrimitive()) {
624                  mh = ObjectClassGenerator.createGuardBoxedPrimitiveSetter(ct, generateSetter(ct, ct), mh);
625             }
626         } else {
627             final Class<?> forType = isUndefined() ? type : getLocalType();
628             mh = generateSetter(!forType.isPrimitive() ? Object.class : forType, type);
629         }
630 
631         if (isBuiltin()) {
632            mh = MH.filterArguments(mh, 0, debugInvalidate(MH.insertArguments(INVALIDATE_SP, 0, this), getKey().toString()));
633         }
634 
635         assert mh.type().returnType() == void.class : mh.type();
636 
637         return mh;
638     }
639 
640     @Override
canChangeType()641     public final boolean canChangeType() {
642         if (!hasDualFields()) {
643             return false;
644         }
645         // Return true for currently undefined even if non-writable/configurable to allow initialization of ES6 CONST.
646         return getLocalType() == null || (getLocalType() != Object.class && (isConfigurable() || isWritable()));
647     }
648 
needsInvalidator(final int typeIndex, final int currentTypeIndex)649     private boolean needsInvalidator(final int typeIndex, final int currentTypeIndex) {
650         return canChangeType() && typeIndex > currentTypeIndex;
651     }
652 
debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag)653     private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
654         if (!Context.DEBUG || !Global.hasInstance()) {
655             return mh;
656         }
657 
658         final Context context = Context.getContextTrusted();
659         assert context != null;
660 
661         return context.addLoggingToHandle(
662                 ObjectClassGenerator.class,
663                 Level.INFO,
664                 mh,
665                 0,
666                 true,
667                 new Supplier<String>() {
668                     @Override
669                     public String get() {
670                         return tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", slot=" + getSlot() + " " + getClass().getSimpleName() + " forType=" + stripName(forType) + ", type=" + stripName(type) + ')';
671                     }
672                 });
673     }
674 
675     private MethodHandle debugReplace(final Class<?> oldType, final Class<?> newType, final PropertyMap oldMap, final PropertyMap newMap) {
676         if (!Context.DEBUG || !Global.hasInstance()) {
677             return REPLACE_MAP;
678         }
679 
680         final Context context = Context.getContextTrusted();
681         assert context != null;
682 
683         MethodHandle mh = context.addLoggingToHandle(
684                 ObjectClassGenerator.class,
685                 REPLACE_MAP,
686                 new Supplier<String>() {
687                     @Override
688                     public String get() {
689                         return "Type change for '" + getKey() + "' " + oldType + "=>" + newType;
690                     }
691                 });
692 
693         mh = context.addLoggingToHandle(
694                 ObjectClassGenerator.class,
695                 Level.FINEST,
696                 mh,
697                 Integer.MAX_VALUE,
698                 false,
699                 new Supplier<String>() {
700                     @Override
701                     public String get() {
702                         return "Setting map " + Debug.id(oldMap) + " => " + Debug.id(newMap) + " " + oldMap + " => " + newMap;
703                     }
704                 });
705         return mh;
706     }
707 
708     private static MethodHandle debugInvalidate(final MethodHandle invalidator, final String key) {
709         if (!Context.DEBUG || !Global.hasInstance()) {
710             return invalidator;
711         }
712 
713         final Context context = Context.getContextTrusted();
714         assert context != null;
715 
716         return context.addLoggingToHandle(
717                 ObjectClassGenerator.class,
718                 invalidator,
719                 new Supplier<String>() {
720                     @Override
721                     public String get() {
722                         return "Field change callback for " + key + " triggered ";
723                     }
724                 });
725     }
726 
727     private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
728         return MH.findStatic(LOOKUP, AccessorProperty.class, name, MH.type(rtype, types));
729     }
730 }
731