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