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.ACCESSOR_TYPES;
  29 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
  30 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.LOG;
  31 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
  32 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_TYPE;
  33 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGetter;
  34 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createGuardBoxedPrimitiveSetter;
  35 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.createSetter;
  36 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorType;
  37 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getAccessorTypeIndex;
  38 import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getNumberOfAccessorTypes;
  39 import static jdk.nashorn.internal.lookup.Lookup.MH;
  40 import static jdk.nashorn.internal.lookup.MethodHandleFactory.stripName;
  41 
  42 import java.lang.invoke.MethodHandle;
  43 import java.lang.invoke.MethodHandles;
  44 import java.lang.invoke.MethodType;
  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.lookup.MethodHandleFactory;
  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 final class AccessorProperty extends Property {
  55     private static final MethodHandles.Lookup lookup = MethodHandles.lookup();
  56     private static final MethodHandle REPLACE_MAP = findOwnMH("replaceMap", Object.class, Object.class, PropertyMap.class, String.class, Class.class, Class.class);
  57 
  58     private static final int NOOF_TYPES = getNumberOfAccessorTypes();
  59 
  60     /**
  61      * Properties in different maps for the same structure class will share their field getters and setters. This could
  62      * be further extended to other method handles that are looked up in the AccessorProperty constructor, but right now
  63      * these are the most frequently retrieved ones, and lookup of method handle natives only registers in the profiler
  64      * for them.
  65      */
  66     private static ClassValue<GettersSetters> GETTERS_SETTERS = new ClassValue<GettersSetters>() {
  67         @Override
  68         protected GettersSetters computeValue(Class<?> structure) {
  69             return new GettersSetters(structure);
  70         }
  71     };
  72 
  73     /** Property getter cache */
  74     private MethodHandle[] getters = new MethodHandle[NOOF_TYPES];
  75 
  76     private static final MethodType[] ACCESSOR_GETTER_TYPES = new MethodType[NOOF_TYPES];
  77     private static final MethodType[] ACCESSOR_SETTER_TYPES = new MethodType[NOOF_TYPES];
  78     private static final MethodType ACCESSOR_GETTER_PRIMITIVE_TYPE;
  79     private static final MethodType ACCESSOR_SETTER_PRIMITIVE_TYPE;
  80     private static final MethodHandle SPILL_ELEMENT_GETTER;
  81     private static final MethodHandle SPILL_ELEMENT_SETTER;
  82 
  83     private static final int SPILL_CACHE_SIZE = 8;
  84     private static final MethodHandle[] SPILL_ACCESSORS = new MethodHandle[SPILL_CACHE_SIZE * 2];
  85 
  86     static {
  87         MethodType getterPrimitiveType = null;
  88         MethodType setterPrimitiveType = null;
  89 
  90         for (int i = 0; i < NOOF_TYPES; i++) {
  91             final Type type = ACCESSOR_TYPES.get(i);
  92             ACCESSOR_GETTER_TYPES[i] = MH.type(type.getTypeClass(), Object.class);
  93             ACCESSOR_SETTER_TYPES[i] = MH.type(void.class, Object.class, type.getTypeClass());
  94 
  95             if (type == PRIMITIVE_TYPE) {
  96                 getterPrimitiveType = ACCESSOR_GETTER_TYPES[i];
  97                 setterPrimitiveType = ACCESSOR_SETTER_TYPES[i];
  98             }
  99         }
 100 
 101         ACCESSOR_GETTER_PRIMITIVE_TYPE = getterPrimitiveType;
 102         ACCESSOR_SETTER_PRIMITIVE_TYPE = setterPrimitiveType;
 103 
 104         final MethodType spillGetterType = MethodType.methodType(Object[].class, Object.class);
 105         final MethodHandle spillGetter = MH.asType(MH.getter(MethodHandles.lookup(), ScriptObject.class, "spill", Object[].class), spillGetterType);
 106         SPILL_ELEMENT_GETTER = MH.filterArguments(MH.arrayElementGetter(Object[].class), 0, spillGetter);
 107         SPILL_ELEMENT_SETTER = MH.filterArguments(MH.arrayElementSetter(Object[].class), 0, spillGetter);
 108     }
 109 
 110     /**
 111      * Create a new accessor property. Factory method used by nasgen generated code.
 112      *
 113      * @param key           {@link Property} key.
 114      * @param propertyFlags {@link Property} flags.
 115      * @param getter        {@link Property} get accessor method.
 116      * @param setter        {@link Property} set accessor method.
 117      *
 118      * @return  New {@link AccessorProperty} created.
 119      */
 120     public static AccessorProperty create(final String key, final int propertyFlags, final MethodHandle getter, final MethodHandle setter) {
 121         return new AccessorProperty(key, propertyFlags, -1, getter, setter);
 122     }
 123 
 124     /** Seed getter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */
 125     private MethodHandle primitiveGetter;
 126 
 127     /** Seed setter for the primitive version of this field (in -Dnashorn.fields.dual=true mode) */
 128     private MethodHandle primitiveSetter;
 129 
 130     /** Seed getter for the Object version of this field */
 131     private MethodHandle objectGetter;
 132 
 133     /** Seed setter for the Object version of this field */
 134     private MethodHandle objectSetter;
 135 
 136     /**
 137      * Current type of this object, in object only mode, this is an Object.class. In dual-fields mode
 138      * null means undefined, and primitive types are allowed. The reason a special type is used for
 139      * undefined, is that are no bits left to represent it in primitive types
 140      */
 141     private Class<?> currentType;
 142 
 143     /**
 144      * Delegate constructor for bound properties. This is used for properties created by
 145      * {@link ScriptRuntime#mergeScope} and the Nashorn {@code Object.bindProperties} method.
 146      * The former is used to add a script's defined globals to the current global scope while
 147      * still storing them in a JO-prefixed ScriptObject class.
 148      *
 149      * <p>All properties created by this constructor have the {@link #IS_BOUND} flag set.</p>
 150      *
 151      * @param property  accessor property to rebind
 152      * @param delegate  delegate object to rebind receiver to
 153      */
 154     AccessorProperty(final AccessorProperty property, final Object delegate) {
 155         super(property);
 156 
 157         this.primitiveGetter = bindTo(property.primitiveGetter, delegate);
 158         this.primitiveSetter = bindTo(property.primitiveSetter, delegate);
 159         this.objectGetter    = bindTo(property.ensureObjectGetter(), delegate);
 160         this.objectSetter    = bindTo(property.ensureObjectSetter(), delegate);
 161 
 162         // Properties created this way are bound to a delegate
 163         this.flags |= IS_BOUND;
 164         setCurrentType(property.getCurrentType());
 165     }
 166 
 167     /**
 168      * Constructor for spill properties. Array getters and setters will be created on demand.
 169      *
 170      * @param key    the property key
 171      * @param flags  the property flags
 172      * @param slot   spill slot
 173      */
 174     public AccessorProperty(final String key, final int flags, final int slot) {
 175         super(key, flags, slot);
 176         assert (flags & IS_SPILL) == IS_SPILL;
 177 
 178         setCurrentType(Object.class);
 179     }
 180 
 181     /**
 182      * Constructor. Similar to the constructor with both primitive getters and setters, the difference
 183      * here being that only one getter and setter (setter is optional for non writable fields) is given
 184      * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes
 185      *
 186      * @param key    the property key
 187      * @param flags  the property flags
 188      * @param slot   the property field number or spill slot
 189      * @param getter the property getter
 190      * @param setter the property setter or null if non writable, non configurable
 191      */
 192     AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) {
 193         super(key, flags, slot);
 194 
 195         // we don't need to prep the setters these will never be invalidated as this is a nasgen
 196         // or known type getter/setter. No invalidations will take place
 197 
 198         final Class<?> getterType = getter.type().returnType();
 199         final Class<?> setterType = setter == null ? null : setter.type().parameterType(1);
 200 
 201         assert setterType == null || setterType == getterType;
 202 
 203         if (getterType.isPrimitive()) {
 204             for (int i = 0; i < NOOF_TYPES; i++) {
 205                 getters[i] = MH.asType(
 206                     Lookup.filterReturnType(
 207                         getter,
 208                         getAccessorType(i).getTypeClass()),
 209                     ACCESSOR_GETTER_TYPES[i]);
 210             }
 211         } else {
 212             objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter;
 213             objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter;
 214         }
 215 
 216         setCurrentType(getterType);
 217     }
 218 
 219     private static class GettersSetters {
 220         final MethodHandle[] getters;
 221         final MethodHandle[] setters;
 222 
 223         public GettersSetters(Class<?> structure) {
 224             final int fieldCount = ObjectClassGenerator.getFieldCount(structure);
 225             getters = new MethodHandle[fieldCount];
 226             setters = new MethodHandle[fieldCount];
 227             for(int i = 0; i < fieldCount; ++i) {
 228                 final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT);
 229                 getters[i] = MH.asType(MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.GET_OBJECT_TYPE);
 230                 setters[i] = MH.asType(MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.SET_OBJECT_TYPE);
 231             }
 232         }
 233     }
 234 
 235     /**
 236      * Constructor for dual field AccessorPropertys.
 237      *
 238      * @param key              property key
 239      * @param flags            property flags
 240      * @param structure        structure for objects associated with this property
 241      * @param slot             property field number or spill slot
 242      */
 243     public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) {
 244         super(key, flags, slot);
 245 
 246         /*
 247          * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also
 248          * works in dual field mode, it only means that the property never has a primitive
 249          * representation.
 250          */
 251         primitiveGetter = null;
 252         primitiveSetter = null;
 253 
 254         if (isParameter() && hasArguments()) {
 255             final MethodHandle arguments   = MH.getter(lookup, structure, "arguments", ScriptObject.class);
 256 
 257             objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE);
 258             objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE);
 259         } else {
 260             final GettersSetters gs = GETTERS_SETTERS.get(structure);
 261             objectGetter = gs.getters[slot];
 262             objectSetter = gs.setters[slot];
 263 
 264             if (!OBJECT_FIELDS_ONLY) {
 265                 final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, PRIMITIVE_TYPE);
 266                 final Class<?> typeClass = PRIMITIVE_TYPE.getTypeClass();
 267                 primitiveGetter = MH.asType(MH.getter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_GETTER_PRIMITIVE_TYPE);
 268                 primitiveSetter = MH.asType(MH.setter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_SETTER_PRIMITIVE_TYPE);
 269             }
 270         }
 271 
 272         Class<?> initialType = null;
 273 
 274         if (OBJECT_FIELDS_ONLY || isAlwaysObject()) {
 275             initialType = Object.class;
 276         } else if (!canBePrimitive()) {
 277             info(key + " cannot be primitive");
 278             initialType = Object.class;
 279         } else {
 280             info(key + " CAN be primitive");
 281             if (!canBeUndefined()) {
 282                 info(key + " is always defined");
 283                 initialType = int.class; //double works too for less type invalidation, but this requires experimentation, e.g. var x = 17; x += 2 will turn it into double now because of lack of range analysis
 284             }
 285         }
 286 
 287         // is always object means "is never initialized to undefined, and always of object type
 288         setCurrentType(initialType);
 289     }
 290 
 291     /**
 292      * Copy constructor
 293      *
 294      * @param property  source property
 295      */
 296     protected AccessorProperty(final AccessorProperty property) {
 297         super(property);
 298 
 299         this.getters         = property.getters;
 300         this.primitiveGetter = property.primitiveGetter;
 301         this.primitiveSetter = property.primitiveSetter;
 302         this.objectGetter    = property.objectGetter;
 303         this.objectSetter    = property.objectSetter;
 304 
 305         setCurrentType(property.getCurrentType());
 306     }
 307 
 308     private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
 309         if (mh == null) {
 310             return null;
 311         }
 312 
 313         return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class);
 314     }
 315 
 316     @Override
 317     protected Property copy() {
 318         return new AccessorProperty(this);
 319     }
 320 
 321     @Override
 322     public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict)  {
 323         if (isSpill()) {
 324             self.spill[getSlot()] = value;
 325         } else {
 326             try {
 327                 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
 328             } catch (final Error|RuntimeException e) {
 329                 throw e;
 330             } catch (final Throwable e) {
 331                 throw new RuntimeException(e);
 332             }
 333         }
 334     }
 335 
 336     @Override
 337     public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
 338         if (isSpill()) {
 339             return self.spill[getSlot()];
 340         }
 341 
 342         try {
 343             return getGetter(Object.class).invokeExact((Object)self);
 344         } catch (final Error|RuntimeException e) {
 345             throw e;
 346         } catch (final Throwable e) {
 347             throw new RuntimeException(e);
 348         }
 349     }
 350 
 351     // Spill getters and setters are lazily initialized, see JDK-8011630
 352     private MethodHandle ensureObjectGetter() {
 353         if (isSpill() && objectGetter == null) {
 354             objectGetter = getSpillGetter();
 355         }
 356         return objectGetter;
 357     }
 358 
 359     private MethodHandle ensureObjectSetter() {
 360         if (isSpill() && objectSetter == null) {
 361             objectSetter = getSpillSetter();
 362         }
 363         return objectSetter;
 364     }
 365 
 366     @Override
 367     public MethodHandle getGetter(final Class<?> type) {
 368         final int i = getAccessorTypeIndex(type);
 369         ensureObjectGetter();
 370 
 371         if (getters[i] == null) {
 372             getters[i] = debug(
 373                 createGetter(currentType, type, primitiveGetter, objectGetter),
 374                 currentType, type, "get");
 375         }
 376 
 377         return getters[i];
 378     }
 379 
 380     private Property getWiderProperty(final Class<?> type) {
 381         final AccessorProperty newProperty = new AccessorProperty(this);
 382         newProperty.invalidate(type);
 383         return newProperty;
 384     }
 385 
 386     private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
 387         final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
 388         assert oldMap.size() > 0;
 389         assert newMap.size() == oldMap.size();
 390         return newMap;
 391     }
 392 
 393     // the final three arguments are for debug printout purposes only
 394     @SuppressWarnings("unused")
 395     private static Object replaceMap(final Object sobj, final PropertyMap newMap, final String key, final Class<?> oldType, final Class<?> newType) {
 396         if (DEBUG_FIELDS) {
 397             final PropertyMap oldMap = ((ScriptObject)sobj).getMap();
 398             info("Type change for '" + key + "' " + oldType + "=>" + newType);
 399             finest("setting map " + sobj + " from " + Debug.id(oldMap) + " to " + Debug.id(newMap) + " " + oldMap + " => " + newMap);
 400         }
 401         ((ScriptObject)sobj).setMap(newMap);
 402         return sobj;
 403     }
 404 
 405     private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
 406         ensureObjectSetter();
 407         MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter);
 408         mh = debug(mh, currentType, type, "set");
 409         return mh;
 410     }
 411 
 412     @Override
 413     public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
 414         final int i            = getAccessorTypeIndex(type);
 415         final int ci           = currentType == null ? -1 : getAccessorTypeIndex(currentType);
 416         final Class<?> forType = currentType == null ? type : currentType;
 417 
 418         //if we are asking for an object setter, but are still a primitive type, we might try to box it
 419         MethodHandle mh;
 420 
 421         if (needsInvalidator(i, ci)) {
 422             final Property     newProperty = getWiderProperty(type);
 423             final PropertyMap  newMap      = getWiderMap(currentMap, newProperty);
 424             final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
 425             final MethodHandle explodeTypeSetter = MH.filterArguments(widerSetter, 0, MH.insertArguments(REPLACE_MAP, 1, newMap, getKey(), currentType, type));
 426             if (currentType != null && currentType.isPrimitive() && type == Object.class) {
 427                 //might try a box check on this to avoid widening field to object storage
 428                 mh = createGuardBoxedPrimitiveSetter(currentType, generateSetter(currentType, currentType), explodeTypeSetter);
 429             } else {
 430                 mh = explodeTypeSetter;
 431             }
 432         } else {
 433             mh = generateSetter(forType, type);
 434         }
 435 
 436         return mh;
 437     }
 438 
 439     @Override
 440     public boolean canChangeType() {
 441         if (OBJECT_FIELDS_ONLY) {
 442             return false;
 443         }
 444         return currentType != Object.class && (isConfigurable() || isWritable());
 445     }
 446 
 447     private boolean needsInvalidator(final int ti, final int fti) {
 448         return canChangeType() && ti > fti;
 449     }
 450 
 451     private void invalidate(final Class<?> newType) {
 452         getters = new MethodHandle[NOOF_TYPES];
 453         setCurrentType(newType);
 454     }
 455 
 456     private MethodHandle getSpillGetter() {
 457         final int slot = getSlot();
 458         MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null;
 459         if (getter == null) {
 460             getter = MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot);
 461             if (slot < SPILL_CACHE_SIZE) {
 462                 SPILL_ACCESSORS[slot * 2 + 0] = getter;
 463             }
 464         }
 465         return getter;
 466     }
 467 
 468     private MethodHandle getSpillSetter() {
 469         final int slot = getSlot();
 470         MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null;
 471         if (setter == null) {
 472             setter = MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot);
 473             if (slot < SPILL_CACHE_SIZE) {
 474                 SPILL_ACCESSORS[slot * 2 + 1] = setter;
 475             }
 476         }
 477         return setter;
 478     }
 479 
 480     private static void finest(final String str) {
 481         if (DEBUG_FIELDS) {
 482             LOG.finest(str);
 483         }
 484     }
 485 
 486     private static void info(final String str) {
 487         if (DEBUG_FIELDS) {
 488             LOG.info(str);
 489         }
 490     }
 491 
 492     private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
 493         if (DEBUG_FIELDS) {
 494            return MethodHandleFactory.addDebugPrintout(
 495                LOG,
 496                mh,
 497                tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')');
 498         }
 499         return mh;
 500     }
 501 
 502     private void setCurrentType(final Class<?> currentType) {
 503         this.currentType = currentType;
 504     }
 505 
 506     @Override
 507     public Class<?> getCurrentType() {
 508         return currentType;
 509     }
 510 
 511     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 512         return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types));
 513     }
 514 
 515 }