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. This is used when adding properties to the Global scope, which
 145      * is necessary for outermost levels in a script (the ScriptObject is represented by
 146      * a JO-prefixed ScriptObject class, but the properties need to be in the Global scope
 147      * and are thus rebound with that as receiver
 148      *
 149      * @param property  accessor property to rebind
 150      * @param delegate  delegate object to rebind receiver to
 151      */
 152     AccessorProperty(final AccessorProperty property, final Object delegate) {
 153         super(property);
 154 
 155         this.primitiveGetter = bindTo(property.primitiveGetter, delegate);
 156         this.primitiveSetter = bindTo(property.primitiveSetter, delegate);
 157         this.objectGetter    = bindTo(property.ensureObjectGetter(), delegate);
 158         this.objectSetter    = bindTo(property.ensureObjectSetter(), delegate);
 159 
 160         this.flags |= IS_BOUND;
 161         setCurrentType(property.getCurrentType());
 162     }
 163 
 164     /**
 165      * Constructor for spill properties. Array getters and setters will be created on demand.
 166      *
 167      * @param key    the property key
 168      * @param flags  the property flags
 169      * @param slot   spill slot
 170      */
 171     public AccessorProperty(final String key, final int flags, final int slot) {
 172         super(key, flags, slot);
 173         assert (flags & IS_SPILL) == IS_SPILL;
 174 
 175         setCurrentType(Object.class);
 176     }
 177 
 178     /**
 179      * Constructor. Similar to the constructor with both primitive getters and setters, the difference
 180      * here being that only one getter and setter (setter is optional for non writable fields) is given
 181      * to the constructor, and the rest are created from those. Used e.g. by Nasgen classes
 182      *
 183      * @param key    the property key
 184      * @param flags  the property flags
 185      * @param slot   the property field number or spill slot
 186      * @param getter the property getter
 187      * @param setter the property setter or null if non writable, non configurable
 188      */
 189     AccessorProperty(final String key, final int flags, final int slot, final MethodHandle getter, final MethodHandle setter) {
 190         super(key, flags, slot);
 191 
 192         // we don't need to prep the setters these will never be invalidated as this is a nasgen
 193         // or known type getter/setter. No invalidations will take place
 194 
 195         final Class<?> getterType = getter.type().returnType();
 196         final Class<?> setterType = setter == null ? null : setter.type().parameterType(1);
 197 
 198         assert setterType == null || setterType == getterType;
 199 
 200         if (getterType.isPrimitive()) {
 201             for (int i = 0; i < NOOF_TYPES; i++) {
 202                 getters[i] = MH.asType(
 203                     Lookup.filterReturnType(
 204                         getter,
 205                         getAccessorType(i).getTypeClass()),
 206                     ACCESSOR_GETTER_TYPES[i]);
 207             }
 208         } else {
 209             objectGetter = getter.type() != Lookup.GET_OBJECT_TYPE ? MH.asType(getter, Lookup.GET_OBJECT_TYPE) : getter;
 210             objectSetter = setter != null && setter.type() != Lookup.SET_OBJECT_TYPE ? MH.asType(setter, Lookup.SET_OBJECT_TYPE) : setter;
 211         }
 212 
 213         setCurrentType(getterType);
 214     }
 215 
 216     private static class GettersSetters {
 217         final MethodHandle[] getters;
 218         final MethodHandle[] setters;
 219 
 220         public GettersSetters(Class<?> structure) {
 221             final int fieldCount = ObjectClassGenerator.getFieldCount(structure);
 222             getters = new MethodHandle[fieldCount];
 223             setters = new MethodHandle[fieldCount];
 224             for(int i = 0; i < fieldCount; ++i) {
 225                 final String fieldName = ObjectClassGenerator.getFieldName(i, Type.OBJECT);
 226                 getters[i] = MH.asType(MH.getter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.GET_OBJECT_TYPE);
 227                 setters[i] = MH.asType(MH.setter(lookup, structure, fieldName, Type.OBJECT.getTypeClass()), Lookup.SET_OBJECT_TYPE);
 228             }
 229         }
 230     }
 231 
 232     /**
 233      * Constructor for dual field AccessorPropertys.
 234      *
 235      * @param key              property key
 236      * @param flags            property flags
 237      * @param structure        structure for objects associated with this property
 238      * @param slot             property field number or spill slot
 239      */
 240     public AccessorProperty(final String key, final int flags, final Class<?> structure, final int slot) {
 241         super(key, flags, slot);
 242 
 243         /*
 244          * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also
 245          * works in dual field mode, it only means that the property never has a primitive
 246          * representation.
 247          */
 248         primitiveGetter = null;
 249         primitiveSetter = null;
 250 
 251         if (isParameter() && hasArguments()) {
 252             final MethodHandle arguments   = MH.getter(lookup, structure, "arguments", ScriptObject.class);
 253 
 254             objectGetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.GET_OBJECT_TYPE);
 255             objectSetter = MH.asType(MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, arguments), 1, slot), Lookup.SET_OBJECT_TYPE);
 256         } else {
 257             final GettersSetters gs = GETTERS_SETTERS.get(structure);
 258             objectGetter = gs.getters[slot];
 259             objectSetter = gs.setters[slot];
 260 
 261             if (!OBJECT_FIELDS_ONLY) {
 262                 final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(slot, PRIMITIVE_TYPE);
 263                 final Class<?> typeClass = PRIMITIVE_TYPE.getTypeClass();
 264                 primitiveGetter = MH.asType(MH.getter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_GETTER_PRIMITIVE_TYPE);
 265                 primitiveSetter = MH.asType(MH.setter(lookup, structure, fieldNamePrimitive, typeClass), ACCESSOR_SETTER_PRIMITIVE_TYPE);
 266             }
 267         }
 268 
 269         Class<?> initialType = null;
 270 
 271         if (OBJECT_FIELDS_ONLY || isAlwaysObject()) {
 272             initialType = Object.class;
 273         } else if (!canBePrimitive()) {
 274             info(key + " cannot be primitive");
 275             initialType = Object.class;
 276         } else {
 277             info(key + " CAN be primitive");
 278             if (!canBeUndefined()) {
 279                 info(key + " is always defined");
 280                 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
 281             }
 282         }
 283 
 284         // is always object means "is never initialized to undefined, and always of object type
 285         setCurrentType(initialType);
 286     }
 287 
 288     /**
 289      * Copy constructor
 290      *
 291      * @param property  source property
 292      */
 293     protected AccessorProperty(final AccessorProperty property) {
 294         super(property);
 295 
 296         this.getters         = property.getters;
 297         this.primitiveGetter = property.primitiveGetter;
 298         this.primitiveSetter = property.primitiveSetter;
 299         this.objectGetter    = property.objectGetter;
 300         this.objectSetter    = property.objectSetter;
 301 
 302         setCurrentType(property.getCurrentType());
 303     }
 304 
 305     private static MethodHandle bindTo(final MethodHandle mh, final Object receiver) {
 306         if (mh == null) {
 307             return null;
 308         }
 309 
 310         return MH.dropArguments(MH.bindTo(mh, receiver), 0, Object.class);
 311     }
 312 
 313     @Override
 314     protected Property copy() {
 315         return new AccessorProperty(this);
 316     }
 317 
 318     @Override
 319     public void setObjectValue(final ScriptObject self, final ScriptObject owner, final Object value, final boolean strict)  {
 320         if (isSpill()) {
 321             self.spill[getSlot()] = value;
 322         } else {
 323             try {
 324                 getSetter(Object.class, self.getMap()).invokeExact((Object)self, value);
 325             } catch (final Error|RuntimeException e) {
 326                 throw e;
 327             } catch (final Throwable e) {
 328                 throw new RuntimeException(e);
 329             }
 330         }
 331     }
 332 
 333     @Override
 334     public Object getObjectValue(final ScriptObject self, final ScriptObject owner) {
 335         if (isSpill()) {
 336             return self.spill[getSlot()];
 337         }
 338 
 339         try {
 340             return getGetter(Object.class).invokeExact((Object)self);
 341         } catch (final Error|RuntimeException e) {
 342             throw e;
 343         } catch (final Throwable e) {
 344             throw new RuntimeException(e);
 345         }
 346     }
 347 
 348     // Spill getters and setters are lazily initialized, see JDK-8011630
 349     private MethodHandle ensureObjectGetter() {
 350         if (isSpill() && objectGetter == null) {
 351             objectGetter = getSpillGetter();
 352         }
 353         return objectGetter;
 354     }
 355 
 356     private MethodHandle ensureObjectSetter() {
 357         if (isSpill() && objectSetter == null) {
 358             objectSetter = getSpillSetter();
 359         }
 360         return objectSetter;
 361     }
 362 
 363     @Override
 364     public MethodHandle getGetter(final Class<?> type) {
 365         final int i = getAccessorTypeIndex(type);
 366         ensureObjectGetter();
 367 
 368         if (getters[i] == null) {
 369             getters[i] = debug(
 370                 createGetter(currentType, type, primitiveGetter, objectGetter),
 371                 currentType, type, "get");
 372         }
 373 
 374         return getters[i];
 375     }
 376 
 377     private Property getWiderProperty(final Class<?> type) {
 378         final AccessorProperty newProperty = new AccessorProperty(this);
 379         newProperty.invalidate(type);
 380         return newProperty;
 381     }
 382 
 383     private PropertyMap getWiderMap(final PropertyMap oldMap, final Property newProperty) {
 384         final PropertyMap newMap = oldMap.replaceProperty(this, newProperty);
 385         assert oldMap.size() > 0;
 386         assert newMap.size() == oldMap.size();
 387         return newMap;
 388     }
 389 
 390     // the final three arguments are for debug printout purposes only
 391     @SuppressWarnings("unused")
 392     private static Object replaceMap(final Object sobj, final PropertyMap newMap, final String key, final Class<?> oldType, final Class<?> newType) {
 393         if (DEBUG_FIELDS) {
 394             final PropertyMap oldMap = ((ScriptObject)sobj).getMap();
 395             info("Type change for '" + key + "' " + oldType + "=>" + newType);
 396             finest("setting map " + sobj + " from " + Debug.id(oldMap) + " to " + Debug.id(newMap) + " " + oldMap + " => " + newMap);
 397         }
 398         ((ScriptObject)sobj).setMap(newMap);
 399         return sobj;
 400     }
 401 
 402     private MethodHandle generateSetter(final Class<?> forType, final Class<?> type) {
 403         ensureObjectSetter();
 404         MethodHandle mh = createSetter(forType, type, primitiveSetter, objectSetter);
 405         mh = debug(mh, currentType, type, "set");
 406         return mh;
 407     }
 408 
 409     @Override
 410     public MethodHandle getSetter(final Class<?> type, final PropertyMap currentMap) {
 411         final int i            = getAccessorTypeIndex(type);
 412         final int ci           = currentType == null ? -1 : getAccessorTypeIndex(currentType);
 413         final Class<?> forType = currentType == null ? type : currentType;
 414 
 415         //if we are asking for an object setter, but are still a primitive type, we might try to box it
 416         MethodHandle mh;
 417 
 418         if (needsInvalidator(i, ci)) {
 419             final Property     newProperty = getWiderProperty(type);
 420             final PropertyMap  newMap      = getWiderMap(currentMap, newProperty);
 421             final MethodHandle widerSetter = newProperty.getSetter(type, newMap);
 422             final MethodHandle explodeTypeSetter = MH.filterArguments(widerSetter, 0, MH.insertArguments(REPLACE_MAP, 1, newMap, getKey(), currentType, type));
 423             if (currentType != null && currentType.isPrimitive() && type == Object.class) {
 424                 //might try a box check on this to avoid widening field to object storage
 425                 mh = createGuardBoxedPrimitiveSetter(currentType, generateSetter(currentType, currentType), explodeTypeSetter);
 426             } else {
 427                 mh = explodeTypeSetter;
 428             }
 429         } else {
 430             mh = generateSetter(forType, type);
 431         }
 432 
 433         return mh;
 434     }
 435 
 436     @Override
 437     public boolean canChangeType() {
 438         if (OBJECT_FIELDS_ONLY) {
 439             return false;
 440         }
 441         return currentType != Object.class && (isConfigurable() || isWritable());
 442     }
 443 
 444     private boolean needsInvalidator(final int ti, final int fti) {
 445         return canChangeType() && ti > fti;
 446     }
 447 
 448     private void invalidate(final Class<?> newType) {
 449         getters = new MethodHandle[NOOF_TYPES];
 450         setCurrentType(newType);
 451     }
 452 
 453     private MethodHandle getSpillGetter() {
 454         final int slot = getSlot();
 455         MethodHandle getter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2] : null;
 456         if (getter == null) {
 457             getter = MH.insertArguments(SPILL_ELEMENT_GETTER, 1, slot);
 458             if (slot < SPILL_CACHE_SIZE) {
 459                 SPILL_ACCESSORS[slot * 2 + 0] = getter;
 460             }
 461         }
 462         return getter;
 463     }
 464 
 465     private MethodHandle getSpillSetter() {
 466         final int slot = getSlot();
 467         MethodHandle setter = slot < SPILL_CACHE_SIZE ? SPILL_ACCESSORS[slot * 2 + 1] : null;
 468         if (setter == null) {
 469             setter = MH.insertArguments(SPILL_ELEMENT_SETTER, 1, slot);
 470             if (slot < SPILL_CACHE_SIZE) {
 471                 SPILL_ACCESSORS[slot * 2 + 1] = setter;
 472             }
 473         }
 474         return setter;
 475     }
 476 
 477     private static void finest(final String str) {
 478         if (DEBUG_FIELDS) {
 479             LOG.finest(str);
 480         }
 481     }
 482 
 483     private static void info(final String str) {
 484         if (DEBUG_FIELDS) {
 485             LOG.info(str);
 486         }
 487     }
 488 
 489     private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
 490         if (DEBUG_FIELDS) {
 491            return MethodHandleFactory.addDebugPrintout(
 492                LOG,
 493                mh,
 494                tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')');
 495         }
 496         return mh;
 497     }
 498 
 499     private void setCurrentType(final Class<?> currentType) {
 500         this.currentType = currentType;
 501     }
 502 
 503     @Override
 504     public Class<?> getCurrentType() {
 505         return currentType;
 506     }
 507 
 508     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 509         return MH.findStatic(lookup, AccessorProperty.class, name, MH.type(rtype, types));
 510     }
 511 
 512 }
--- EOF ---