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