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.objects;
  27 
  28 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  29 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  30 import static jdk.nashorn.internal.lookup.Lookup.MH;
  31 
  32 import java.lang.invoke.MethodHandle;
  33 import java.lang.invoke.MethodHandles;
  34 import java.util.Arrays;
  35 import java.util.BitSet;
  36 import jdk.nashorn.internal.runtime.Property;
  37 import jdk.nashorn.internal.runtime.PropertyDescriptor;
  38 import jdk.nashorn.internal.runtime.PropertyMap;
  39 import jdk.nashorn.internal.runtime.ScriptFunction;
  40 import jdk.nashorn.internal.runtime.ScriptObject;
  41 import jdk.nashorn.internal.runtime.ScriptRuntime;
  42 import jdk.nashorn.internal.runtime.arrays.ArrayData;
  43 import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
  44 import jdk.nashorn.internal.lookup.Lookup;
  45 
  46 /**
  47  * ECMA 10.6 Arguments Object.
  48  *
  49  * Arguments object used for non-strict mode functions. For strict mode, we use
  50  * a different implementation (@see NativeStrictArguments). In non-strict mode,
  51  * named argument access and index argument access (arguments[i]) are linked.
  52  * Modifications reflect on each other access -- till arguments indexed element
  53  * is deleted. After delete, there is no link between named access and indexed
  54  * access for that deleted index alone.
  55  */
  56 public final class NativeArguments extends ScriptObject {
  57 
  58     private static final MethodHandle G$LENGTH = findOwnMH("G$length", Object.class, Object.class);
  59     private static final MethodHandle S$LENGTH = findOwnMH("S$length", void.class, Object.class, Object.class);
  60     private static final MethodHandle G$CALLEE = findOwnMH("G$callee", Object.class, Object.class);
  61     private static final MethodHandle S$CALLEE = findOwnMH("S$callee", void.class, Object.class, Object.class);
  62 
  63     private static final PropertyMap nasgenmap$;
  64 
  65     static {
  66         PropertyMap map = PropertyMap.newMap(NativeArguments.class);
  67         map = Lookup.newProperty(map, "length", Property.NOT_ENUMERABLE, G$LENGTH, S$LENGTH);
  68         map = Lookup.newProperty(map, "callee", Property.NOT_ENUMERABLE, G$CALLEE, S$CALLEE);
  69         nasgenmap$ = map;
  70     }
  71 
  72     private Object length;
  73     private Object callee;
  74     private ArrayData namedArgs;
  75     // This is lazily initialized - only when delete is invoked at all
  76     private BitSet deleted;
  77 
  78     NativeArguments(final Object[] arguments, final Object callee, final int numParams) {
  79         super(nasgenmap$);
  80         setIsArguments();
  81 
  82         setArray(ArrayData.allocate(arguments));
  83         this.length = arguments.length;
  84         this.callee = callee;
  85 
  86         /**
  87          * Declared number of parameters may be more or less than the actual passed
  88          * runtime arguments count. We need to truncate or extend with undefined values.
  89          *
  90          * Example:
  91          *
  92          * // less declared params
  93          * (function (x) { print(arguments); })(20, 44);
  94          *
  95          * // more declared params
  96          * (function (x, y) { print(arguments); })(3);
  97          */
  98         final Object[] newValues = new Object[numParams];
  99         if (numParams > arguments.length) {
 100             Arrays.fill(newValues, UNDEFINED);
 101         }
 102         System.arraycopy(arguments, 0, newValues, 0, Math.min(newValues.length, arguments.length));
 103         this.namedArgs = ArrayData.allocate(newValues);
 104 
 105         // set Object.prototype as __proto__
 106         this.setProto(Global.objectPrototype());
 107     }
 108 
 109     @Override
 110     public String getClassName() {
 111         return "Arguments";
 112     }
 113 
 114     /**
 115      * getArgument is used for named argument access.
 116      */
 117     @Override
 118     public Object getArgument(final int key) {
 119         return namedArgs.has(key) ? namedArgs.getObject(key) : UNDEFINED;
 120     }
 121 
 122     /**
 123      * setArgument is used for named argument set.
 124      */
 125     @Override
 126     public void setArgument(final int key, final Object value) {
 127         if (namedArgs.has(key)) {
 128             namedArgs.set(key, value, false);
 129         }
 130     }
 131 
 132     @Override
 133     public int getInt(final Object key) {
 134         final int index = ArrayIndex.getArrayIndex(key);
 135         return isMapped(index) ? namedArgs.getInt(index) : super.getInt(key);
 136     }
 137 
 138     @Override
 139     public int getInt(final double key) {
 140         final int index = ArrayIndex.getArrayIndex(key);
 141         return isMapped(index) ? namedArgs.getInt(index) : super.getInt(key);
 142     }
 143 
 144     @Override
 145     public int getInt(final long key) {
 146         final int index = ArrayIndex.getArrayIndex(key);
 147         return isMapped(index) ? namedArgs.getInt(index) : super.getInt(key);
 148     }
 149 
 150     @Override
 151     public int getInt(final int key) {
 152         final int index = ArrayIndex.getArrayIndex(key);
 153         return isMapped(index) ? namedArgs.getInt(index) : super.getInt(key);
 154     }
 155 
 156     @Override
 157     public long getLong(final Object key) {
 158         final int index = ArrayIndex.getArrayIndex(key);
 159         return isMapped(index) ? namedArgs.getLong(index) : super.getLong(key);
 160     }
 161 
 162     @Override
 163     public long getLong(final double key) {
 164         final int index = ArrayIndex.getArrayIndex(key);
 165         return isMapped(index) ? namedArgs.getLong(index) : super.getLong(key);
 166     }
 167 
 168     @Override
 169     public long getLong(final long key) {
 170         final int index = ArrayIndex.getArrayIndex(key);
 171         return isMapped(index) ? namedArgs.getLong(index) : super.getLong(key);
 172     }
 173 
 174     @Override
 175     public long getLong(final int key) {
 176         final int index = ArrayIndex.getArrayIndex(key);
 177         return isMapped(index) ? namedArgs.getLong(index) : super.getLong(key);
 178     }
 179 
 180     @Override
 181     public double getDouble(final Object key) {
 182         final int index = ArrayIndex.getArrayIndex(key);
 183         return isMapped(index) ? namedArgs.getDouble(index) : super.getDouble(key);
 184     }
 185 
 186     @Override
 187     public double getDouble(final double key) {
 188         final int index = ArrayIndex.getArrayIndex(key);
 189         return isMapped(index) ? namedArgs.getDouble(index) : super.getDouble(key);
 190     }
 191 
 192     @Override
 193     public double getDouble(final long key) {
 194         final int index = ArrayIndex.getArrayIndex(key);
 195         return isMapped(index) ? namedArgs.getDouble(index) : super.getDouble(key);
 196     }
 197 
 198     @Override
 199     public double getDouble(final int key) {
 200         final int index = ArrayIndex.getArrayIndex(key);
 201         return isMapped(index) ? namedArgs.getDouble(index) : super.getDouble(key);
 202     }
 203 
 204     @Override
 205     public Object get(final Object key) {
 206         final int index = ArrayIndex.getArrayIndex(key);
 207         return isMapped(index) ? namedArgs.getObject(index) : super.get(key);
 208     }
 209 
 210     @Override
 211     public Object get(final double key) {
 212         final int index = ArrayIndex.getArrayIndex(key);
 213         return isMapped(index) ? namedArgs.getObject(index) : super.get(key);
 214     }
 215 
 216     @Override
 217     public Object get(final long key) {
 218         final int index = ArrayIndex.getArrayIndex(key);
 219         return isMapped(index) ? namedArgs.getObject(index) : super.get(key);
 220     }
 221 
 222     @Override
 223     public Object get(final int key) {
 224         final int index = ArrayIndex.getArrayIndex(key);
 225         return isMapped(index) ? namedArgs.getObject(index) : super.get(key);
 226     }
 227 
 228     @Override
 229     public void set(final Object key, final int value, final boolean strict) {
 230         final int index = ArrayIndex.getArrayIndex(key);
 231         if (isMapped(index)) {
 232             namedArgs = namedArgs.set(index, value, strict);
 233         } else {
 234             super.set(key, value, strict);
 235         }
 236     }
 237 
 238     @Override
 239     public void set(final Object key, final long value, final boolean strict) {
 240         final int index = ArrayIndex.getArrayIndex(key);
 241         if (isMapped(index)) {
 242             namedArgs = namedArgs.set(index, value, strict);
 243         } else {
 244             super.set(key, value, strict);
 245         }
 246     }
 247 
 248     @Override
 249     public void set(final Object key, final double value, final boolean strict) {
 250         final int index = ArrayIndex.getArrayIndex(key);
 251         if (isMapped(index)) {
 252             namedArgs = namedArgs.set(index, value, strict);
 253         } else {
 254             super.set(key, value, strict);
 255         }
 256     }
 257 
 258     @Override
 259     public void set(final Object key, final Object value, final boolean strict) {
 260         final int index = ArrayIndex.getArrayIndex(key);
 261         if (isMapped(index)) {
 262             namedArgs = namedArgs.set(index, value, strict);
 263         } else {
 264             super.set(key, value, strict);
 265         }
 266     }
 267 
 268     @Override
 269     public void set(final double key, final int value, final boolean strict) {
 270         final int index = ArrayIndex.getArrayIndex(key);
 271         if (isMapped(index)) {
 272             namedArgs = namedArgs.set(index, value, strict);
 273         } else {
 274             super.set(key, value, strict);
 275         }
 276     }
 277 
 278     @Override
 279     public void set(final double key, final long value, final boolean strict) {
 280         final int index = ArrayIndex.getArrayIndex(key);
 281         if (isMapped(index)) {
 282             namedArgs = namedArgs.set(index, value, strict);
 283         } else {
 284             super.set(key, value, strict);
 285         }
 286     }
 287 
 288     @Override
 289     public void set(final double key, final double value, final boolean strict) {
 290         final int index = ArrayIndex.getArrayIndex(key);
 291         if (isMapped(index)) {
 292             namedArgs = namedArgs.set(index, value, strict);
 293         } else {
 294             super.set(key, value, strict);
 295         }
 296     }
 297 
 298     @Override
 299     public void set(final double key, final Object value, final boolean strict) {
 300         final int index = ArrayIndex.getArrayIndex(key);
 301         if (isMapped(index)) {
 302             namedArgs = namedArgs.set(index, value, strict);
 303         } else {
 304             super.set(key, value, strict);
 305         }
 306     }
 307 
 308     @Override
 309     public void set(final long key, final int value, final boolean strict) {
 310         final int index = ArrayIndex.getArrayIndex(key);
 311         if (isMapped(index)) {
 312             namedArgs = namedArgs.set(index, value, strict);
 313         } else {
 314             super.set(key, value, strict);
 315         }
 316     }
 317 
 318     @Override
 319     public void set(final long key, final long value, final boolean strict) {
 320         final int index = ArrayIndex.getArrayIndex(key);
 321         if (isMapped(index)) {
 322             namedArgs = namedArgs.set(index, value, strict);
 323         } else {
 324             super.set(key, value, strict);
 325         }
 326     }
 327 
 328     @Override
 329     public void set(final long key, final double value, final boolean strict) {
 330         final int index = ArrayIndex.getArrayIndex(key);
 331         if (isMapped(index)) {
 332             namedArgs = namedArgs.set(index, value, strict);
 333         } else {
 334             super.set(key, value, strict);
 335         }
 336     }
 337 
 338     @Override
 339     public void set(final long key, final Object value, final boolean strict) {
 340         final int index = ArrayIndex.getArrayIndex(key);
 341         if (isMapped(index)) {
 342             namedArgs = namedArgs.set(index, value, strict);
 343         } else {
 344             super.set(key, value, strict);
 345         }
 346     }
 347 
 348     @Override
 349     public void set(final int key, final int value, final boolean strict) {
 350         final int index = ArrayIndex.getArrayIndex(key);
 351         if (isMapped(index)) {
 352             namedArgs = namedArgs.set(index, value, strict);
 353         } else {
 354             super.set(key, value, strict);
 355         }
 356     }
 357 
 358     @Override
 359     public void set(final int key, final long value, final boolean strict) {
 360         final int index = ArrayIndex.getArrayIndex(key);
 361         if (isMapped(index)) {
 362             namedArgs = namedArgs.set(index, value, strict);
 363         } else {
 364             super.set(key, value, strict);
 365         }
 366     }
 367 
 368     @Override
 369     public void set(final int key, final double value, final boolean strict) {
 370         final int index = ArrayIndex.getArrayIndex(key);
 371         if (isMapped(index)) {
 372             namedArgs = namedArgs.set(index, value, strict);
 373         } else {
 374             super.set(key, value, strict);
 375         }
 376     }
 377 
 378     @Override
 379     public void set(final int key, final Object value, final boolean strict) {
 380         final int index = ArrayIndex.getArrayIndex(key);
 381         if (isMapped(index)) {
 382             namedArgs = namedArgs.set(index, value, strict);
 383         } else {
 384             super.set(key, value, strict);
 385         }
 386     }
 387 
 388     @Override
 389     public boolean has(final Object key) {
 390         final int index = ArrayIndex.getArrayIndex(key);
 391         return isMapped(index) || super.has(key);
 392     }
 393 
 394     @Override
 395     public boolean has(final double key) {
 396         final int index = ArrayIndex.getArrayIndex(key);
 397         return isMapped(index) || super.has(key);
 398     }
 399 
 400     @Override
 401     public boolean has(final long key) {
 402         final int index = ArrayIndex.getArrayIndex(key);
 403         return isMapped(index) || super.has(key);
 404     }
 405 
 406     @Override
 407     public boolean has(final int key) {
 408         final int index = ArrayIndex.getArrayIndex(key);
 409         return isMapped(index) || super.has(key);
 410     }
 411 
 412     @Override
 413     public boolean hasOwnProperty(final Object key) {
 414         final int index = ArrayIndex.getArrayIndex(key);
 415         return isMapped(index) || super.hasOwnProperty(key);
 416     }
 417 
 418     @Override
 419     public boolean hasOwnProperty(final int key) {
 420         final int index = ArrayIndex.getArrayIndex(key);
 421         return isMapped(index) || super.hasOwnProperty(key);
 422     }
 423 
 424     @Override
 425     public boolean hasOwnProperty(final long key) {
 426         final int index = ArrayIndex.getArrayIndex(key);
 427         return isMapped(index) || super.hasOwnProperty(key);
 428     }
 429 
 430     @Override
 431     public boolean hasOwnProperty(final double key) {
 432         final int index = ArrayIndex.getArrayIndex(key);
 433         return isMapped(index) || super.hasOwnProperty(key);
 434     }
 435 
 436     @Override
 437     public boolean delete(final int key, final boolean strict) {
 438         final int index = ArrayIndex.getArrayIndex(key);
 439         final boolean success = super.delete(key, strict);
 440         if (success && namedArgs.has(index)) {
 441             setDeleted(index);
 442         }
 443         return success;
 444     }
 445 
 446     @Override
 447     public boolean delete(final long key, final boolean strict) {
 448         final int index = ArrayIndex.getArrayIndex(key);
 449         final boolean success = super.delete(key, strict);
 450         if (success && namedArgs.has(index)) {
 451             setDeleted(index);
 452         }
 453         return success;
 454     }
 455 
 456     @Override
 457     public boolean delete(final double key, final boolean strict) {
 458         final int index = ArrayIndex.getArrayIndex(key);
 459         final boolean success = super.delete(key, strict);
 460         if (success && namedArgs.has(index)) {
 461             setDeleted(index);
 462         }
 463         return success;
 464     }
 465 
 466     @Override
 467     public boolean delete(final Object key, final boolean strict) {
 468         final int index = ArrayIndex.getArrayIndex(key);
 469         final boolean success = super.delete(key, strict);
 470         if (success && namedArgs.has(index)) {
 471             setDeleted(index);
 472         }
 473         return success;
 474     }
 475 
 476     /**
 477      * ECMA 15.4.5.1 [[DefineOwnProperty]] ( P, Desc, Throw ) as specialized in
 478      * ECMA 10.6 for Arguments object.
 479      */
 480     @Override
 481     public boolean defineOwnProperty(final String key, final Object propertyDesc, final boolean reject) {
 482         final int index = ArrayIndex.getArrayIndex(key);
 483         if (index >= 0) {
 484             final boolean allowed = super.defineOwnProperty(key, propertyDesc, false);
 485             if (!allowed) {
 486                 if (reject) {
 487                     throw typeError("cant.redefine.property",  key, ScriptRuntime.safeToString(this));
 488                 }
 489                 return false;
 490             }
 491 
 492             if (isMapped(index)) {
 493                 // When mapped argument is redefined, if new descriptor is accessor property
 494                 // or data-non-writable property, we have to "unmap" (unlink).
 495                 final PropertyDescriptor desc = toPropertyDescriptor(Global.instance(), propertyDesc);
 496                 if (desc.type() == PropertyDescriptor.ACCESSOR) {
 497                     setDeleted(index);
 498                 } else {
 499                     // set "value" from new descriptor to named args
 500                     if (desc.has(PropertyDescriptor.VALUE)) {
 501                         namedArgs = namedArgs.set(index, desc.getValue(), false);
 502                     }
 503 
 504                     if (desc.has(PropertyDescriptor.WRITABLE) && !desc.isWritable()) {
 505                         setDeleted(index);
 506                     }
 507                 }
 508             }
 509 
 510             return true;
 511         }
 512 
 513         return super.defineOwnProperty(key, propertyDesc, reject);
 514     }
 515 
 516     // Internals below this point
 517 
 518     // We track deletions using a bit set (delete arguments[index])
 519     private boolean isDeleted(final int index) {
 520         return (deleted != null) ? deleted.get(index) : false;
 521     }
 522 
 523     private void setDeleted(final int index) {
 524         if (deleted == null) {
 525             deleted = new BitSet((int)namedArgs.length());
 526         }
 527         deleted.set(index, true);
 528     }
 529 
 530     /**
 531      * Are arguments[index] and corresponding named parameter linked?
 532      *
 533      * In non-strict mode, arguments[index] and corresponding named param
 534      * are "linked" or "mapped". Modifications are tacked b/w each other - till
 535      * (delete arguments[index]) is used. Once deleted, the corresponding arg
 536      * is no longer 'mapped'. Please note that delete can happen only through
 537      * the arguments array - named param can not be deleted. (delete is one-way).
 538      */
 539     private boolean isMapped(final int index) {
 540         // in named args and not marked as "deleted"
 541         return namedArgs.has(index) && !isDeleted(index);
 542     }
 543 
 544         /**
 545      * Factory to create correct Arguments object based on strict mode.
 546      *
 547      * @param arguments the actual arguments array passed
 548      * @param callee the callee function that uses arguments object
 549      * @param numParams the number of declared (named) function parameters
 550      * @return Arguments Object
 551      */
 552     public static ScriptObject allocate(final Object[] arguments, final ScriptFunction callee, final int numParams) {
 553         // Strict functions won't always have a callee for arguments, and will pass null instead.
 554         final boolean isStrict = callee == null || callee.isStrict();
 555         return isStrict ? new NativeStrictArguments(arguments, numParams) : new NativeArguments(arguments, callee, numParams);
 556     }
 557 
 558     /**
 559      * Length getter
 560      * @param self self reference
 561      * @return length property value
 562      */
 563     public static Object G$length(final Object self) {
 564         if (self instanceof NativeArguments) {
 565             return ((NativeArguments)self).getArgumentsLength();
 566         }
 567 
 568         return 0;
 569     }
 570 
 571     /**
 572      * Length setter
 573      * @param self self reference
 574      * @param value value for length property
 575      */
 576     public static void S$length(final Object self, final Object value) {
 577         if (self instanceof NativeArguments) {
 578             ((NativeArguments)self).setArgumentsLength(value);
 579         }
 580     }
 581 
 582     /**
 583      * Callee getter
 584      * @param self self reference
 585      * @return value for callee property
 586      */
 587     public static Object G$callee(final Object self) {
 588         if (self instanceof NativeArguments) {
 589             return ((NativeArguments)self).getCallee();
 590         }
 591         return UNDEFINED;
 592     }
 593 
 594     /**
 595      * Callee setter
 596      * @param self self reference
 597      * @param value value for callee property
 598      */
 599     public static void S$callee(final Object self, final Object value) {
 600         if (self instanceof NativeArguments) {
 601             ((NativeArguments)self).setCallee(value);
 602         }
 603     }
 604 
 605     @Override
 606     public Object getLength() {
 607         return length;
 608     }
 609 
 610     private Object getArgumentsLength() {
 611         return length;
 612     }
 613 
 614     private void setArgumentsLength(final Object length) {
 615         this.length = length;
 616     }
 617 
 618     private Object getCallee() {
 619         return callee;
 620     }
 621 
 622     private void setCallee(final Object callee) {
 623         this.callee = callee;
 624     }
 625 
 626     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 627         return MH.findStatic(MethodHandles.publicLookup(), NativeArguments.class, name, MH.type(rtype, types));
 628     }
 629 
 630 }