1 /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 2 * 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // API class 8 9 package org.mozilla.javascript; 10 11 import java.io.IOException; 12 import java.io.ObjectInputStream; 13 import java.io.ObjectOutputStream; 14 import java.io.Serializable; 15 import java.lang.annotation.Annotation; 16 import java.lang.reflect.AccessibleObject; 17 import java.lang.reflect.Constructor; 18 import java.lang.reflect.InvocationTargetException; 19 import java.lang.reflect.Member; 20 import java.lang.reflect.Method; 21 import java.lang.reflect.Modifier; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.Map; 25 26 import org.mozilla.javascript.debug.DebuggableObject; 27 import org.mozilla.javascript.annotations.JSConstructor; 28 import org.mozilla.javascript.annotations.JSFunction; 29 import org.mozilla.javascript.annotations.JSGetter; 30 import org.mozilla.javascript.annotations.JSSetter; 31 import org.mozilla.javascript.annotations.JSStaticFunction; 32 33 /** 34 * This is the default implementation of the Scriptable interface. This 35 * class provides convenient default behavior that makes it easier to 36 * define host objects. 37 * <p> 38 * Various properties and methods of JavaScript objects can be conveniently 39 * defined using methods of ScriptableObject. 40 * <p> 41 * Classes extending ScriptableObject must define the getClassName method. 42 * 43 * @see org.mozilla.javascript.Scriptable 44 * @author Norris Boyd 45 */ 46 47 public abstract class ScriptableObject implements Scriptable, Serializable, 48 DebuggableObject, 49 ConstProperties 50 { 51 52 static final long serialVersionUID = 2829861078851942586L; 53 54 /** 55 * The empty property attribute. 56 * 57 * Used by getAttributes() and setAttributes(). 58 * 59 * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) 60 * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) 61 */ 62 public static final int EMPTY = 0x00; 63 64 /** 65 * Property attribute indicating assignment to this property is ignored. 66 * 67 * @see org.mozilla.javascript.ScriptableObject 68 * #put(String, Scriptable, Object) 69 * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) 70 * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) 71 */ 72 public static final int READONLY = 0x01; 73 74 /** 75 * Property attribute indicating property is not enumerated. 76 * 77 * Only enumerated properties will be returned by getIds(). 78 * 79 * @see org.mozilla.javascript.ScriptableObject#getIds() 80 * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) 81 * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) 82 */ 83 public static final int DONTENUM = 0x02; 84 85 /** 86 * Property attribute indicating property cannot be deleted. 87 * 88 * @see org.mozilla.javascript.ScriptableObject#delete(String) 89 * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) 90 * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) 91 */ 92 public static final int PERMANENT = 0x04; 93 94 /** 95 * Property attribute indicating that this is a const property that has not 96 * been assigned yet. The first 'const' assignment to the property will 97 * clear this bit. 98 */ 99 public static final int UNINITIALIZED_CONST = 0x08; 100 101 public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST; 102 /** 103 * The prototype of this object. 104 */ 105 private Scriptable prototypeObject; 106 107 /** 108 * The parent scope of this object. 109 */ 110 private Scriptable parentScopeObject; 111 112 private transient Slot[] slots; 113 // If count >= 0, it gives number of keys or if count < 0, 114 // it indicates sealed object where ~count gives number of keys 115 private int count; 116 117 // gateways into the definition-order linked list of slots 118 private transient Slot firstAdded; 119 private transient Slot lastAdded; 120 121 122 private volatile Map<Object,Object> associatedValues; 123 124 private static final int SLOT_QUERY = 1; 125 private static final int SLOT_MODIFY = 2; 126 private static final int SLOT_MODIFY_CONST = 3; 127 private static final int SLOT_MODIFY_GETTER_SETTER = 4; 128 private static final int SLOT_CONVERT_ACCESSOR_TO_DATA = 5; 129 130 // initial slot array size, must be a power of 2 131 private static final int INITIAL_SLOT_SIZE = 4; 132 133 private boolean isExtensible = true; 134 135 private static class Slot implements Serializable 136 { 137 private static final long serialVersionUID = -6090581677123995491L; 138 String name; // This can change due to caching 139 int indexOrHash; 140 private volatile short attributes; 141 transient volatile boolean wasDeleted; 142 volatile Object value; 143 transient Slot next; // next in hash table bucket 144 transient volatile Slot orderedNext; // next in linked list 145 Slot(String name, int indexOrHash, int attributes)146 Slot(String name, int indexOrHash, int attributes) 147 { 148 this.name = name; 149 this.indexOrHash = indexOrHash; 150 this.attributes = (short)attributes; 151 } 152 readObject(ObjectInputStream in)153 private void readObject(ObjectInputStream in) 154 throws IOException, ClassNotFoundException 155 { 156 in.defaultReadObject(); 157 if (name != null) { 158 indexOrHash = name.hashCode(); 159 } 160 } 161 setValue(Object value, Scriptable owner, Scriptable start)162 boolean setValue(Object value, Scriptable owner, Scriptable start) { 163 if ((attributes & READONLY) != 0) { 164 return true; 165 } 166 if (owner == start) { 167 this.value = value; 168 return true; 169 } else { 170 return false; 171 } 172 } 173 getValue(Scriptable start)174 Object getValue(Scriptable start) { 175 return value; 176 } 177 getAttributes()178 int getAttributes() 179 { 180 return attributes; 181 } 182 setAttributes(int value)183 synchronized void setAttributes(int value) 184 { 185 checkValidAttributes(value); 186 attributes = (short)value; 187 } 188 markDeleted()189 void markDeleted() { 190 wasDeleted = true; 191 value = null; 192 name = null; 193 } 194 getPropertyDescriptor(Context cx, Scriptable scope)195 ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) { 196 return buildDataDescriptor(scope, value, attributes); 197 } 198 199 } 200 buildDataDescriptor(Scriptable scope, Object value, int attributes)201 protected static ScriptableObject buildDataDescriptor(Scriptable scope, 202 Object value, 203 int attributes) { 204 ScriptableObject desc = new NativeObject(); 205 ScriptRuntime.setBuiltinProtoAndParent(desc, scope, TopLevel.Builtins.Object); 206 desc.defineProperty("value", value, EMPTY); 207 desc.defineProperty("writable", (attributes & READONLY) == 0, EMPTY); 208 desc.defineProperty("enumerable", (attributes & DONTENUM) == 0, EMPTY); 209 desc.defineProperty("configurable", (attributes & PERMANENT) == 0, EMPTY); 210 return desc; 211 } 212 213 private static final class GetterSlot extends Slot 214 { 215 static final long serialVersionUID = -4900574849788797588L; 216 217 Object getter; 218 Object setter; 219 GetterSlot(String name, int indexOrHash, int attributes)220 GetterSlot(String name, int indexOrHash, int attributes) 221 { 222 super(name, indexOrHash, attributes); 223 } 224 225 @Override getPropertyDescriptor(Context cx, Scriptable scope)226 ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) { 227 int attr = getAttributes(); 228 ScriptableObject desc = new NativeObject(); 229 ScriptRuntime.setBuiltinProtoAndParent(desc, scope, TopLevel.Builtins.Object); 230 desc.defineProperty("enumerable", (attr & DONTENUM) == 0, EMPTY); 231 desc.defineProperty("configurable", (attr & PERMANENT) == 0, EMPTY); 232 if (getter != null) desc.defineProperty("get", getter, EMPTY); 233 if (setter != null) desc.defineProperty("set", setter, EMPTY); 234 return desc; 235 } 236 237 @Override setValue(Object value, Scriptable owner, Scriptable start)238 boolean setValue(Object value, Scriptable owner, Scriptable start) { 239 if (setter == null) { 240 if (getter != null) { 241 if (Context.getContext().hasFeature(Context.FEATURE_STRICT_MODE)) { 242 // Based on TC39 ES3.1 Draft of 9-Feb-2009, 8.12.4, step 2, 243 // we should throw a TypeError in this case. 244 throw ScriptRuntime.typeError1("msg.set.prop.no.setter", name); 245 } 246 // Assignment to a property with only a getter defined. The 247 // assignment is ignored. See bug 478047. 248 return true; 249 } 250 } else { 251 Context cx = Context.getContext(); 252 if (setter instanceof MemberBox) { 253 MemberBox nativeSetter = (MemberBox)setter; 254 Class<?> pTypes[] = nativeSetter.argTypes; 255 // XXX: cache tag since it is already calculated in 256 // defineProperty ? 257 Class<?> valueType = pTypes[pTypes.length - 1]; 258 int tag = FunctionObject.getTypeTag(valueType); 259 Object actualArg = FunctionObject.convertArg(cx, start, 260 value, tag); 261 Object setterThis; 262 Object[] args; 263 if (nativeSetter.delegateTo == null) { 264 setterThis = start; 265 args = new Object[] { actualArg }; 266 } else { 267 setterThis = nativeSetter.delegateTo; 268 args = new Object[] { start, actualArg }; 269 } 270 nativeSetter.invoke(setterThis, args); 271 } else if (setter instanceof Function) { 272 Function f = (Function)setter; 273 f.call(cx, f.getParentScope(), start, 274 new Object[] { value }); 275 } 276 return true; 277 } 278 return super.setValue(value, owner, start); 279 } 280 281 @Override getValue(Scriptable start)282 Object getValue(Scriptable start) { 283 if (getter != null) { 284 if (getter instanceof MemberBox) { 285 MemberBox nativeGetter = (MemberBox)getter; 286 Object getterThis; 287 Object[] args; 288 if (nativeGetter.delegateTo == null) { 289 getterThis = start; 290 args = ScriptRuntime.emptyArgs; 291 } else { 292 getterThis = nativeGetter.delegateTo; 293 args = new Object[] { start }; 294 } 295 return nativeGetter.invoke(getterThis, args); 296 } else if (getter instanceof Function) { 297 Function f = (Function)getter; 298 Context cx = Context.getContext(); 299 return f.call(cx, f.getParentScope(), start, 300 ScriptRuntime.emptyArgs); 301 } 302 } 303 Object val = this.value; 304 if (val instanceof LazilyLoadedCtor) { 305 LazilyLoadedCtor initializer = (LazilyLoadedCtor)val; 306 try { 307 initializer.init(); 308 } finally { 309 this.value = val = initializer.getValue(); 310 } 311 } 312 return val; 313 } 314 315 @Override markDeleted()316 void markDeleted() { 317 super.markDeleted(); 318 getter = null; 319 setter = null; 320 } 321 } 322 323 /** 324 * A wrapper around a slot that allows the slot to be used in a new slot 325 * table while keeping it functioning in its old slot table/linked list 326 * context. This is used when linked slots are copied to a new slot table. 327 * In a multi-threaded environment, these slots may still be accessed 328 * through their old slot table. See bug 688458. 329 */ 330 private static class RelinkedSlot extends Slot { 331 332 final Slot slot; 333 RelinkedSlot(Slot slot)334 RelinkedSlot(Slot slot) { 335 super(slot.name, slot.indexOrHash, slot.attributes); 336 // Make sure we always wrap the actual slot, not another relinked one 337 this.slot = unwrapSlot(slot); 338 } 339 340 @Override setValue(Object value, Scriptable owner, Scriptable start)341 boolean setValue(Object value, Scriptable owner, Scriptable start) { 342 return slot.setValue(value, owner, start); 343 } 344 345 @Override getValue(Scriptable start)346 Object getValue(Scriptable start) { 347 return slot.getValue(start); 348 } 349 350 @Override getPropertyDescriptor(Context cx, Scriptable scope)351 ScriptableObject getPropertyDescriptor(Context cx, Scriptable scope) { 352 return slot.getPropertyDescriptor(cx, scope); 353 } 354 355 @Override getAttributes()356 int getAttributes() { 357 return slot.getAttributes(); 358 } 359 360 @Override setAttributes(int value)361 void setAttributes(int value) { 362 slot.setAttributes(value); 363 } 364 365 @Override markDeleted()366 void markDeleted() { 367 super.markDeleted(); 368 slot.markDeleted(); 369 } 370 writeObject(ObjectOutputStream out)371 private void writeObject(ObjectOutputStream out) throws IOException { 372 out.writeObject(slot); // just serialize the wrapped slot 373 } 374 375 } 376 checkValidAttributes(int attributes)377 static void checkValidAttributes(int attributes) 378 { 379 final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST; 380 if ((attributes & ~mask) != 0) { 381 throw new IllegalArgumentException(String.valueOf(attributes)); 382 } 383 } 384 ScriptableObject()385 public ScriptableObject() 386 { 387 } 388 ScriptableObject(Scriptable scope, Scriptable prototype)389 public ScriptableObject(Scriptable scope, Scriptable prototype) 390 { 391 if (scope == null) 392 throw new IllegalArgumentException(); 393 394 parentScopeObject = scope; 395 prototypeObject = prototype; 396 } 397 398 /** 399 * Gets the value that will be returned by calling the typeof operator on this object. 400 * @return default is "object" unless {@link #avoidObjectDetection()} is <code>true</code> in which 401 * case it returns "undefined" 402 */ getTypeOf()403 public String getTypeOf() { 404 return avoidObjectDetection() ? "undefined" : "object"; 405 } 406 407 /** 408 * Return the name of the class. 409 * 410 * This is typically the same name as the constructor. 411 * Classes extending ScriptableObject must implement this abstract 412 * method. 413 */ getClassName()414 public abstract String getClassName(); 415 416 /** 417 * Returns true if the named property is defined. 418 * 419 * @param name the name of the property 420 * @param start the object in which the lookup began 421 * @return true if and only if the property was found in the object 422 */ has(String name, Scriptable start)423 public boolean has(String name, Scriptable start) 424 { 425 return null != getSlot(name, 0, SLOT_QUERY); 426 } 427 428 /** 429 * Returns true if the property index is defined. 430 * 431 * @param index the numeric index for the property 432 * @param start the object in which the lookup began 433 * @return true if and only if the property was found in the object 434 */ has(int index, Scriptable start)435 public boolean has(int index, Scriptable start) 436 { 437 return null != getSlot(null, index, SLOT_QUERY); 438 } 439 440 /** 441 * Returns the value of the named property or NOT_FOUND. 442 * 443 * If the property was created using defineProperty, the 444 * appropriate getter method is called. 445 * 446 * @param name the name of the property 447 * @param start the object in which the lookup began 448 * @return the value of the property (may be null), or NOT_FOUND 449 */ get(String name, Scriptable start)450 public Object get(String name, Scriptable start) 451 { 452 Slot slot = getSlot(name, 0, SLOT_QUERY); 453 if (slot == null) { 454 return Scriptable.NOT_FOUND; 455 } 456 return slot.getValue(start); 457 } 458 459 /** 460 * Returns the value of the indexed property or NOT_FOUND. 461 * 462 * @param index the numeric index for the property 463 * @param start the object in which the lookup began 464 * @return the value of the property (may be null), or NOT_FOUND 465 */ get(int index, Scriptable start)466 public Object get(int index, Scriptable start) 467 { 468 Slot slot = getSlot(null, index, SLOT_QUERY); 469 if (slot == null) { 470 return Scriptable.NOT_FOUND; 471 } 472 return slot.getValue(start); 473 } 474 475 /** 476 * Sets the value of the named property, creating it if need be. 477 * 478 * If the property was created using defineProperty, the 479 * appropriate setter method is called. <p> 480 * 481 * If the property's attributes include READONLY, no action is 482 * taken. 483 * This method will actually set the property in the start 484 * object. 485 * 486 * @param name the name of the property 487 * @param start the object whose property is being set 488 * @param value value to set the property to 489 */ put(String name, Scriptable start, Object value)490 public void put(String name, Scriptable start, Object value) 491 { 492 if (putImpl(name, 0, start, value)) 493 return; 494 495 if (start == this) throw Kit.codeBug(); 496 start.put(name, start, value); 497 } 498 499 /** 500 * Sets the value of the indexed property, creating it if need be. 501 * 502 * @param index the numeric index for the property 503 * @param start the object whose property is being set 504 * @param value value to set the property to 505 */ put(int index, Scriptable start, Object value)506 public void put(int index, Scriptable start, Object value) 507 { 508 if (putImpl(null, index, start, value)) 509 return; 510 511 if (start == this) throw Kit.codeBug(); 512 start.put(index, start, value); 513 } 514 515 /** 516 * Removes a named property from the object. 517 * 518 * If the property is not found, or it has the PERMANENT attribute, 519 * no action is taken. 520 * 521 * @param name the name of the property 522 */ delete(String name)523 public void delete(String name) 524 { 525 checkNotSealed(name, 0); 526 removeSlot(name, 0); 527 } 528 529 /** 530 * Removes the indexed property from the object. 531 * 532 * If the property is not found, or it has the PERMANENT attribute, 533 * no action is taken. 534 * 535 * @param index the numeric index for the property 536 */ delete(int index)537 public void delete(int index) 538 { 539 checkNotSealed(null, index); 540 removeSlot(null, index); 541 } 542 543 /** 544 * Sets the value of the named const property, creating it if need be. 545 * 546 * If the property was created using defineProperty, the 547 * appropriate setter method is called. <p> 548 * 549 * If the property's attributes include READONLY, no action is 550 * taken. 551 * This method will actually set the property in the start 552 * object. 553 * 554 * @param name the name of the property 555 * @param start the object whose property is being set 556 * @param value value to set the property to 557 */ putConst(String name, Scriptable start, Object value)558 public void putConst(String name, Scriptable start, Object value) 559 { 560 if (putConstImpl(name, 0, start, value, READONLY)) 561 return; 562 563 if (start == this) throw Kit.codeBug(); 564 if (start instanceof ConstProperties) 565 ((ConstProperties)start).putConst(name, start, value); 566 else 567 start.put(name, start, value); 568 } 569 defineConst(String name, Scriptable start)570 public void defineConst(String name, Scriptable start) 571 { 572 if (putConstImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST)) 573 return; 574 575 if (start == this) throw Kit.codeBug(); 576 if (start instanceof ConstProperties) 577 ((ConstProperties)start).defineConst(name, start); 578 } 579 /** 580 * Returns true if the named property is defined as a const on this object. 581 * @param name 582 * @return true if the named property is defined as a const, false 583 * otherwise. 584 */ isConst(String name)585 public boolean isConst(String name) 586 { 587 Slot slot = getSlot(name, 0, SLOT_QUERY); 588 if (slot == null) { 589 return false; 590 } 591 return (slot.getAttributes() & (PERMANENT|READONLY)) == 592 (PERMANENT|READONLY); 593 594 } 595 /** 596 * @deprecated Use {@link #getAttributes(String name)}. The engine always 597 * ignored the start argument. 598 */ getAttributes(String name, Scriptable start)599 public final int getAttributes(String name, Scriptable start) 600 { 601 return getAttributes(name); 602 } 603 604 /** 605 * @deprecated Use {@link #getAttributes(int index)}. The engine always 606 * ignored the start argument. 607 */ getAttributes(int index, Scriptable start)608 public final int getAttributes(int index, Scriptable start) 609 { 610 return getAttributes(index); 611 } 612 613 /** 614 * @deprecated Use {@link #setAttributes(String name, int attributes)}. 615 * The engine always ignored the start argument. 616 */ setAttributes(String name, Scriptable start, int attributes)617 public final void setAttributes(String name, Scriptable start, 618 int attributes) 619 { 620 setAttributes(name, attributes); 621 } 622 623 /** 624 * @deprecated Use {@link #setAttributes(int index, int attributes)}. 625 * The engine always ignored the start argument. 626 */ setAttributes(int index, Scriptable start, int attributes)627 public void setAttributes(int index, Scriptable start, 628 int attributes) 629 { 630 setAttributes(index, attributes); 631 } 632 633 /** 634 * Get the attributes of a named property. 635 * 636 * The property is specified by <code>name</code> 637 * as defined for <code>has</code>.<p> 638 * 639 * @param name the identifier for the property 640 * @return the bitset of attributes 641 * @exception EvaluatorException if the named property is not found 642 * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) 643 * @see org.mozilla.javascript.ScriptableObject#READONLY 644 * @see org.mozilla.javascript.ScriptableObject#DONTENUM 645 * @see org.mozilla.javascript.ScriptableObject#PERMANENT 646 * @see org.mozilla.javascript.ScriptableObject#EMPTY 647 */ getAttributes(String name)648 public int getAttributes(String name) 649 { 650 return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes(); 651 } 652 653 /** 654 * Get the attributes of an indexed property. 655 * 656 * @param index the numeric index for the property 657 * @exception EvaluatorException if the named property is not found 658 * is not found 659 * @return the bitset of attributes 660 * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) 661 * @see org.mozilla.javascript.ScriptableObject#READONLY 662 * @see org.mozilla.javascript.ScriptableObject#DONTENUM 663 * @see org.mozilla.javascript.ScriptableObject#PERMANENT 664 * @see org.mozilla.javascript.ScriptableObject#EMPTY 665 */ getAttributes(int index)666 public int getAttributes(int index) 667 { 668 return findAttributeSlot(null, index, SLOT_QUERY).getAttributes(); 669 } 670 671 /** 672 * Set the attributes of a named property. 673 * 674 * The property is specified by <code>name</code> 675 * as defined for <code>has</code>.<p> 676 * 677 * The possible attributes are READONLY, DONTENUM, 678 * and PERMANENT. Combinations of attributes 679 * are expressed by the bitwise OR of attributes. 680 * EMPTY is the state of no attributes set. Any unused 681 * bits are reserved for future use. 682 * 683 * @param name the name of the property 684 * @param attributes the bitset of attributes 685 * @exception EvaluatorException if the named property is not found 686 * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) 687 * @see org.mozilla.javascript.ScriptableObject#READONLY 688 * @see org.mozilla.javascript.ScriptableObject#DONTENUM 689 * @see org.mozilla.javascript.ScriptableObject#PERMANENT 690 * @see org.mozilla.javascript.ScriptableObject#EMPTY 691 */ setAttributes(String name, int attributes)692 public void setAttributes(String name, int attributes) 693 { 694 checkNotSealed(name, 0); 695 findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes); 696 } 697 698 /** 699 * Set the attributes of an indexed property. 700 * 701 * @param index the numeric index for the property 702 * @param attributes the bitset of attributes 703 * @exception EvaluatorException if the named property is not found 704 * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) 705 * @see org.mozilla.javascript.ScriptableObject#READONLY 706 * @see org.mozilla.javascript.ScriptableObject#DONTENUM 707 * @see org.mozilla.javascript.ScriptableObject#PERMANENT 708 * @see org.mozilla.javascript.ScriptableObject#EMPTY 709 */ setAttributes(int index, int attributes)710 public void setAttributes(int index, int attributes) 711 { 712 checkNotSealed(null, index); 713 findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes); 714 } 715 716 /** 717 * XXX: write docs. 718 */ setGetterOrSetter(String name, int index, Callable getterOrSetter, boolean isSetter)719 public void setGetterOrSetter(String name, int index, 720 Callable getterOrSetter, boolean isSetter) 721 { 722 setGetterOrSetter(name, index, getterOrSetter, isSetter, false); 723 } 724 setGetterOrSetter(String name, int index, Callable getterOrSetter, boolean isSetter, boolean force)725 private void setGetterOrSetter(String name, int index, Callable getterOrSetter, 726 boolean isSetter, boolean force) 727 { 728 if (name != null && index != 0) 729 throw new IllegalArgumentException(name); 730 731 if (!force) { 732 checkNotSealed(name, index); 733 } 734 735 final GetterSlot gslot; 736 if (isExtensible()) { 737 gslot = (GetterSlot)getSlot(name, index, SLOT_MODIFY_GETTER_SETTER); 738 } else { 739 Slot slot = unwrapSlot(getSlot(name, index, SLOT_QUERY)); 740 if (!(slot instanceof GetterSlot)) 741 return; 742 gslot = (GetterSlot) slot; 743 } 744 745 if (!force) { 746 int attributes = gslot.getAttributes(); 747 if ((attributes & READONLY) != 0) { 748 throw Context.reportRuntimeError1("msg.modify.readonly", name); 749 } 750 } 751 if (isSetter) { 752 gslot.setter = getterOrSetter; 753 } else { 754 gslot.getter = getterOrSetter; 755 } 756 gslot.value = Undefined.instance; 757 } 758 759 /** 760 * Get the getter or setter for a given property. Used by __lookupGetter__ 761 * and __lookupSetter__. 762 * 763 * @param name Name of the object. If nonnull, index must be 0. 764 * @param index Index of the object. If nonzero, name must be null. 765 * @param isSetter If true, return the setter, otherwise return the getter. 766 * @exception IllegalArgumentException if both name and index are nonnull 767 * and nonzero respectively. 768 * @return Null if the property does not exist. Otherwise returns either 769 * the getter or the setter for the property, depending on 770 * the value of isSetter (may be undefined if unset). 771 */ getGetterOrSetter(String name, int index, boolean isSetter)772 public Object getGetterOrSetter(String name, int index, boolean isSetter) 773 { 774 if (name != null && index != 0) 775 throw new IllegalArgumentException(name); 776 Slot slot = unwrapSlot(getSlot(name, index, SLOT_QUERY)); 777 if (slot == null) 778 return null; 779 if (slot instanceof GetterSlot) { 780 GetterSlot gslot = (GetterSlot)slot; 781 Object result = isSetter ? gslot.setter : gslot.getter; 782 return result != null ? result : Undefined.instance; 783 } else 784 return Undefined.instance; 785 } 786 787 /** 788 * Returns whether a property is a getter or a setter 789 * @param name property name 790 * @param index property index 791 * @param setter true to check for a setter, false for a getter 792 * @return whether the property is a getter or a setter 793 */ isGetterOrSetter(String name, int index, boolean setter)794 protected boolean isGetterOrSetter(String name, int index, boolean setter) { 795 Slot slot = unwrapSlot(getSlot(name, index, SLOT_QUERY)); 796 if (slot instanceof GetterSlot) { 797 if (setter && ((GetterSlot)slot).setter != null) return true; 798 if (!setter && ((GetterSlot)slot).getter != null) return true; 799 } 800 return false; 801 } 802 addLazilyInitializedValue(String name, int index, LazilyLoadedCtor init, int attributes)803 void addLazilyInitializedValue(String name, int index, 804 LazilyLoadedCtor init, int attributes) 805 { 806 if (name != null && index != 0) 807 throw new IllegalArgumentException(name); 808 checkNotSealed(name, index); 809 GetterSlot gslot = (GetterSlot)getSlot(name, index, 810 SLOT_MODIFY_GETTER_SETTER); 811 gslot.setAttributes(attributes); 812 gslot.getter = null; 813 gslot.setter = null; 814 gslot.value = init; 815 } 816 817 /** 818 * Returns the prototype of the object. 819 */ getPrototype()820 public Scriptable getPrototype() 821 { 822 return prototypeObject; 823 } 824 825 /** 826 * Sets the prototype of the object. 827 */ setPrototype(Scriptable m)828 public void setPrototype(Scriptable m) 829 { 830 prototypeObject = m; 831 } 832 833 /** 834 * Returns the parent (enclosing) scope of the object. 835 */ getParentScope()836 public Scriptable getParentScope() 837 { 838 return parentScopeObject; 839 } 840 841 /** 842 * Sets the parent (enclosing) scope of the object. 843 */ setParentScope(Scriptable m)844 public void setParentScope(Scriptable m) 845 { 846 parentScopeObject = m; 847 } 848 849 /** 850 * Returns an array of ids for the properties of the object. 851 * 852 * <p>Any properties with the attribute DONTENUM are not listed. <p> 853 * 854 * @return an array of java.lang.Objects with an entry for every 855 * listed property. Properties accessed via an integer index will 856 * have a corresponding 857 * Integer entry in the returned array. Properties accessed by 858 * a String will have a String entry in the returned array. 859 */ getIds()860 public Object[] getIds() { 861 return getIds(false); 862 } 863 864 /** 865 * Returns an array of ids for the properties of the object. 866 * 867 * <p>All properties, even those with attribute DONTENUM, are listed. <p> 868 * 869 * @return an array of java.lang.Objects with an entry for every 870 * listed property. Properties accessed via an integer index will 871 * have a corresponding 872 * Integer entry in the returned array. Properties accessed by 873 * a String will have a String entry in the returned array. 874 */ getAllIds()875 public Object[] getAllIds() { 876 return getIds(true); 877 } 878 879 /** 880 * Implements the [[DefaultValue]] internal method. 881 * 882 * <p>Note that the toPrimitive conversion is a no-op for 883 * every type other than Object, for which [[DefaultValue]] 884 * is called. See ECMA 9.1.<p> 885 * 886 * A <code>hint</code> of null means "no hint". 887 * 888 * @param typeHint the type hint 889 * @return the default value for the object 890 * 891 * See ECMA 8.6.2.6. 892 */ getDefaultValue(Class<?> typeHint)893 public Object getDefaultValue(Class<?> typeHint) 894 { 895 return getDefaultValue(this, typeHint); 896 } 897 getDefaultValue(Scriptable object, Class<?> typeHint)898 public static Object getDefaultValue(Scriptable object, Class<?> typeHint) 899 { 900 Context cx = null; 901 for (int i=0; i < 2; i++) { 902 boolean tryToString; 903 if (typeHint == ScriptRuntime.StringClass) { 904 tryToString = (i == 0); 905 } else { 906 tryToString = (i == 1); 907 } 908 909 String methodName; 910 Object[] args; 911 if (tryToString) { 912 methodName = "toString"; 913 args = ScriptRuntime.emptyArgs; 914 } else { 915 methodName = "valueOf"; 916 args = new Object[1]; 917 String hint; 918 if (typeHint == null) { 919 hint = "undefined"; 920 } else if (typeHint == ScriptRuntime.StringClass) { 921 hint = "string"; 922 } else if (typeHint == ScriptRuntime.ScriptableClass) { 923 hint = "object"; 924 } else if (typeHint == ScriptRuntime.FunctionClass) { 925 hint = "function"; 926 } else if (typeHint == ScriptRuntime.BooleanClass 927 || typeHint == Boolean.TYPE) 928 { 929 hint = "boolean"; 930 } else if (typeHint == ScriptRuntime.NumberClass || 931 typeHint == ScriptRuntime.ByteClass || 932 typeHint == Byte.TYPE || 933 typeHint == ScriptRuntime.ShortClass || 934 typeHint == Short.TYPE || 935 typeHint == ScriptRuntime.IntegerClass || 936 typeHint == Integer.TYPE || 937 typeHint == ScriptRuntime.FloatClass || 938 typeHint == Float.TYPE || 939 typeHint == ScriptRuntime.DoubleClass || 940 typeHint == Double.TYPE) 941 { 942 hint = "number"; 943 } else { 944 throw Context.reportRuntimeError1( 945 "msg.invalid.type", typeHint.toString()); 946 } 947 args[0] = hint; 948 } 949 Object v = getProperty(object, methodName); 950 if (!(v instanceof Function)) 951 continue; 952 Function fun = (Function) v; 953 if (cx == null) 954 cx = Context.getContext(); 955 v = fun.call(cx, fun.getParentScope(), object, args); 956 if (v != null) { 957 if (!(v instanceof Scriptable)) { 958 return v; 959 } 960 if (typeHint == ScriptRuntime.ScriptableClass 961 || typeHint == ScriptRuntime.FunctionClass) 962 { 963 return v; 964 } 965 if (tryToString && v instanceof Wrapper) { 966 // Let a wrapped java.lang.String pass for a primitive 967 // string. 968 Object u = ((Wrapper)v).unwrap(); 969 if (u instanceof String) 970 return u; 971 } 972 } 973 } 974 // fall through to error 975 String arg = (typeHint == null) ? "undefined" : typeHint.getName(); 976 throw ScriptRuntime.typeError1("msg.default.value", arg); 977 } 978 979 /** 980 * Implements the instanceof operator. 981 * 982 * <p>This operator has been proposed to ECMA. 983 * 984 * @param instance The value that appeared on the LHS of the instanceof 985 * operator 986 * @return true if "this" appears in value's prototype chain 987 * 988 */ hasInstance(Scriptable instance)989 public boolean hasInstance(Scriptable instance) { 990 // Default for JS objects (other than Function) is to do prototype 991 // chasing. This will be overridden in NativeFunction and non-JS 992 // objects. 993 994 return ScriptRuntime.jsDelegatesTo(instance, this); 995 } 996 997 /** 998 * Emulate the SpiderMonkey (and Firefox) feature of allowing 999 * custom objects to avoid detection by normal "object detection" 1000 * code patterns. This is used to implement document.all. 1001 * See https://bugzilla.mozilla.org/show_bug.cgi?id=412247. 1002 * This is an analog to JOF_DETECTING from SpiderMonkey; see 1003 * https://bugzilla.mozilla.org/show_bug.cgi?id=248549. 1004 * Other than this special case, embeddings should return false. 1005 * @return true if this object should avoid object detection 1006 * @since 1.7R1 1007 */ avoidObjectDetection()1008 public boolean avoidObjectDetection() { 1009 return false; 1010 } 1011 1012 /** 1013 * Custom <tt>==</tt> operator. 1014 * Must return {@link Scriptable#NOT_FOUND} if this object does not 1015 * have custom equality operator for the given value, 1016 * <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>, 1017 * <tt>Boolean.FALSE</tt> if this object is not equivalent to 1018 * <tt>value</tt>. 1019 * <p> 1020 * The default implementation returns Boolean.TRUE 1021 * if <tt>this == value</tt> or {@link Scriptable#NOT_FOUND} otherwise. 1022 * It indicates that by default custom equality is available only if 1023 * <tt>value</tt> is <tt>this</tt> in which case true is returned. 1024 */ equivalentValues(Object value)1025 protected Object equivalentValues(Object value) 1026 { 1027 return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND; 1028 } 1029 1030 /** 1031 * Defines JavaScript objects from a Java class that implements Scriptable. 1032 * 1033 * If the given class has a method 1034 * <pre> 1035 * static void init(Context cx, Scriptable scope, boolean sealed);</pre> 1036 * 1037 * or its compatibility form 1038 * <pre> 1039 * static void init(Scriptable scope);</pre> 1040 * 1041 * then it is invoked and no further initialization is done.<p> 1042 * 1043 * However, if no such a method is found, then the class's constructors and 1044 * methods are used to initialize a class in the following manner.<p> 1045 * 1046 * First, the zero-parameter constructor of the class is called to 1047 * create the prototype. If no such constructor exists, 1048 * a {@link EvaluatorException} is thrown. <p> 1049 * 1050 * Next, all methods are scanned for special prefixes that indicate that they 1051 * have special meaning for defining JavaScript objects. 1052 * These special prefixes are 1053 * <ul> 1054 * <li><code>jsFunction_</code> for a JavaScript function 1055 * <li><code>jsStaticFunction_</code> for a JavaScript function that 1056 * is a property of the constructor 1057 * <li><code>jsGet_</code> for a getter of a JavaScript property 1058 * <li><code>jsSet_</code> for a setter of a JavaScript property 1059 * <li><code>jsConstructor</code> for a JavaScript function that 1060 * is the constructor 1061 * </ul><p> 1062 * 1063 * If the method's name begins with "jsFunction_", a JavaScript function 1064 * is created with a name formed from the rest of the Java method name 1065 * following "jsFunction_". So a Java method named "jsFunction_foo" will 1066 * define a JavaScript method "foo". Calling this JavaScript function 1067 * will cause the Java method to be called. The parameters of the method 1068 * must be of number and types as defined by the FunctionObject class. 1069 * The JavaScript function is then added as a property 1070 * of the prototype. <p> 1071 * 1072 * If the method's name begins with "jsStaticFunction_", it is handled 1073 * similarly except that the resulting JavaScript function is added as a 1074 * property of the constructor object. The Java method must be static. 1075 * 1076 * If the method's name begins with "jsGet_" or "jsSet_", the method is 1077 * considered to define a property. Accesses to the defined property 1078 * will result in calls to these getter and setter methods. If no 1079 * setter is defined, the property is defined as READONLY.<p> 1080 * 1081 * If the method's name is "jsConstructor", the method is 1082 * considered to define the body of the constructor. Only one 1083 * method of this name may be defined. You may use the varargs forms 1084 * for constructors documented in {@link FunctionObject#FunctionObject(String, Member, Scriptable)} 1085 * 1086 * If no method is found that can serve as constructor, a Java 1087 * constructor will be selected to serve as the JavaScript 1088 * constructor in the following manner. If the class has only one 1089 * Java constructor, that constructor is used to define 1090 * the JavaScript constructor. If the the class has two constructors, 1091 * one must be the zero-argument constructor (otherwise an 1092 * {@link EvaluatorException} would have already been thrown 1093 * when the prototype was to be created). In this case 1094 * the Java constructor with one or more parameters will be used 1095 * to define the JavaScript constructor. If the class has three 1096 * or more constructors, an {@link EvaluatorException} 1097 * will be thrown.<p> 1098 * 1099 * Finally, if there is a method 1100 * <pre> 1101 * static void finishInit(Scriptable scope, FunctionObject constructor, 1102 * Scriptable prototype)</pre> 1103 * 1104 * it will be called to finish any initialization. The <code>scope</code> 1105 * argument will be passed, along with the newly created constructor and 1106 * the newly created prototype.<p> 1107 * 1108 * @param scope The scope in which to define the constructor. 1109 * @param clazz The Java class to use to define the JavaScript objects 1110 * and properties. 1111 * @exception IllegalAccessException if access is not available 1112 * to a reflected class member 1113 * @exception InstantiationException if unable to instantiate 1114 * the named class 1115 * @exception InvocationTargetException if an exception is thrown 1116 * during execution of methods of the named class 1117 * @see org.mozilla.javascript.Function 1118 * @see org.mozilla.javascript.FunctionObject 1119 * @see org.mozilla.javascript.ScriptableObject#READONLY 1120 * @see org.mozilla.javascript.ScriptableObject 1121 * #defineProperty(String, Class, int) 1122 */ defineClass( Scriptable scope, Class<T> clazz)1123 public static <T extends Scriptable> void defineClass( 1124 Scriptable scope, Class<T> clazz) 1125 throws IllegalAccessException, InstantiationException, 1126 InvocationTargetException 1127 { 1128 defineClass(scope, clazz, false, false); 1129 } 1130 1131 /** 1132 * Defines JavaScript objects from a Java class, optionally 1133 * allowing sealing. 1134 * 1135 * Similar to <code>defineClass(Scriptable scope, Class clazz)</code> 1136 * except that sealing is allowed. An object that is sealed cannot have 1137 * properties added or removed. Note that sealing is not allowed in 1138 * the current ECMA/ISO language specification, but is likely for 1139 * the next version. 1140 * 1141 * @param scope The scope in which to define the constructor. 1142 * @param clazz The Java class to use to define the JavaScript objects 1143 * and properties. The class must implement Scriptable. 1144 * @param sealed Whether or not to create sealed standard objects that 1145 * cannot be modified. 1146 * @exception IllegalAccessException if access is not available 1147 * to a reflected class member 1148 * @exception InstantiationException if unable to instantiate 1149 * the named class 1150 * @exception InvocationTargetException if an exception is thrown 1151 * during execution of methods of the named class 1152 * @since 1.4R3 1153 */ defineClass( Scriptable scope, Class<T> clazz, boolean sealed)1154 public static <T extends Scriptable> void defineClass( 1155 Scriptable scope, Class<T> clazz, boolean sealed) 1156 throws IllegalAccessException, InstantiationException, 1157 InvocationTargetException 1158 { 1159 defineClass(scope, clazz, sealed, false); 1160 } 1161 1162 /** 1163 * Defines JavaScript objects from a Java class, optionally 1164 * allowing sealing and mapping of Java inheritance to JavaScript 1165 * prototype-based inheritance. 1166 * 1167 * Similar to <code>defineClass(Scriptable scope, Class clazz)</code> 1168 * except that sealing and inheritance mapping are allowed. An object 1169 * that is sealed cannot have properties added or removed. Note that 1170 * sealing is not allowed in the current ECMA/ISO language specification, 1171 * but is likely for the next version. 1172 * 1173 * @param scope The scope in which to define the constructor. 1174 * @param clazz The Java class to use to define the JavaScript objects 1175 * and properties. The class must implement Scriptable. 1176 * @param sealed Whether or not to create sealed standard objects that 1177 * cannot be modified. 1178 * @param mapInheritance Whether or not to map Java inheritance to 1179 * JavaScript prototype-based inheritance. 1180 * @return the class name for the prototype of the specified class 1181 * @exception IllegalAccessException if access is not available 1182 * to a reflected class member 1183 * @exception InstantiationException if unable to instantiate 1184 * the named class 1185 * @exception InvocationTargetException if an exception is thrown 1186 * during execution of methods of the named class 1187 * @since 1.6R2 1188 */ defineClass( Scriptable scope, Class<T> clazz, boolean sealed, boolean mapInheritance)1189 public static <T extends Scriptable> String defineClass( 1190 Scriptable scope, Class<T> clazz, boolean sealed, 1191 boolean mapInheritance) 1192 throws IllegalAccessException, InstantiationException, 1193 InvocationTargetException 1194 { 1195 BaseFunction ctor = buildClassCtor(scope, clazz, sealed, 1196 mapInheritance); 1197 if (ctor == null) 1198 return null; 1199 String name = ctor.getClassPrototype().getClassName(); 1200 defineProperty(scope, name, ctor, ScriptableObject.DONTENUM); 1201 return name; 1202 } 1203 buildClassCtor( Scriptable scope, Class<T> clazz, boolean sealed, boolean mapInheritance)1204 static <T extends Scriptable> BaseFunction buildClassCtor( 1205 Scriptable scope, Class<T> clazz, 1206 boolean sealed, 1207 boolean mapInheritance) 1208 throws IllegalAccessException, InstantiationException, 1209 InvocationTargetException 1210 { 1211 Method[] methods = FunctionObject.getMethodList(clazz); 1212 for (int i=0; i < methods.length; i++) { 1213 Method method = methods[i]; 1214 if (!method.getName().equals("init")) 1215 continue; 1216 Class<?>[] parmTypes = method.getParameterTypes(); 1217 if (parmTypes.length == 3 && 1218 parmTypes[0] == ScriptRuntime.ContextClass && 1219 parmTypes[1] == ScriptRuntime.ScriptableClass && 1220 parmTypes[2] == Boolean.TYPE && 1221 Modifier.isStatic(method.getModifiers())) 1222 { 1223 Object args[] = { Context.getContext(), scope, 1224 sealed ? Boolean.TRUE : Boolean.FALSE }; 1225 method.invoke(null, args); 1226 return null; 1227 } 1228 if (parmTypes.length == 1 && 1229 parmTypes[0] == ScriptRuntime.ScriptableClass && 1230 Modifier.isStatic(method.getModifiers())) 1231 { 1232 Object args[] = { scope }; 1233 method.invoke(null, args); 1234 return null; 1235 } 1236 1237 } 1238 1239 // If we got here, there isn't an "init" method with the right 1240 // parameter types. 1241 1242 Constructor<?>[] ctors = clazz.getConstructors(); 1243 Constructor<?> protoCtor = null; 1244 for (int i=0; i < ctors.length; i++) { 1245 if (ctors[i].getParameterTypes().length == 0) { 1246 protoCtor = ctors[i]; 1247 break; 1248 } 1249 } 1250 if (protoCtor == null) { 1251 throw Context.reportRuntimeError1( 1252 "msg.zero.arg.ctor", clazz.getName()); 1253 } 1254 1255 Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs); 1256 String className = proto.getClassName(); 1257 1258 // check for possible redefinition 1259 Object existing = getProperty(getTopLevelScope(scope), className); 1260 if (existing instanceof BaseFunction) { 1261 Object existingProto = ((BaseFunction)existing).getPrototypeProperty(); 1262 if (existingProto != null && clazz.equals(existingProto.getClass())) { 1263 return (BaseFunction)existing; 1264 } 1265 } 1266 1267 // Set the prototype's prototype, trying to map Java inheritance to JS 1268 // prototype-based inheritance if requested to do so. 1269 Scriptable superProto = null; 1270 if (mapInheritance) { 1271 Class<? super T> superClass = clazz.getSuperclass(); 1272 if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass) && 1273 !Modifier.isAbstract(superClass.getModifiers())) 1274 { 1275 Class<? extends Scriptable> superScriptable = 1276 extendsScriptable(superClass); 1277 String name = ScriptableObject.defineClass(scope, 1278 superScriptable, sealed, mapInheritance); 1279 if (name != null) { 1280 superProto = ScriptableObject.getClassPrototype(scope, name); 1281 } 1282 } 1283 } 1284 if (superProto == null) { 1285 superProto = ScriptableObject.getObjectPrototype(scope); 1286 } 1287 proto.setPrototype(superProto); 1288 1289 // Find out whether there are any methods that begin with 1290 // "js". If so, then only methods that begin with special 1291 // prefixes will be defined as JavaScript entities. 1292 final String functionPrefix = "jsFunction_"; 1293 final String staticFunctionPrefix = "jsStaticFunction_"; 1294 final String getterPrefix = "jsGet_"; 1295 final String setterPrefix = "jsSet_"; 1296 final String ctorName = "jsConstructor"; 1297 1298 Member ctorMember = findAnnotatedMember(methods, JSConstructor.class); 1299 if (ctorMember == null) { 1300 ctorMember = findAnnotatedMember(ctors, JSConstructor.class); 1301 } 1302 if (ctorMember == null) { 1303 ctorMember = FunctionObject.findSingleMethod(methods, ctorName); 1304 } 1305 if (ctorMember == null) { 1306 if (ctors.length == 1) { 1307 ctorMember = ctors[0]; 1308 } else if (ctors.length == 2) { 1309 if (ctors[0].getParameterTypes().length == 0) 1310 ctorMember = ctors[1]; 1311 else if (ctors[1].getParameterTypes().length == 0) 1312 ctorMember = ctors[0]; 1313 } 1314 if (ctorMember == null) { 1315 throw Context.reportRuntimeError1( 1316 "msg.ctor.multiple.parms", clazz.getName()); 1317 } 1318 } 1319 1320 FunctionObject ctor = new FunctionObject(className, ctorMember, scope); 1321 if (ctor.isVarArgsMethod()) { 1322 throw Context.reportRuntimeError1 1323 ("msg.varargs.ctor", ctorMember.getName()); 1324 } 1325 ctor.initAsConstructor(scope, proto); 1326 1327 Method finishInit = null; 1328 HashSet<String> staticNames = new HashSet<String>(), 1329 instanceNames = new HashSet<String>(); 1330 for (Method method : methods) { 1331 if (method == ctorMember) { 1332 continue; 1333 } 1334 String name = method.getName(); 1335 if (name.equals("finishInit")) { 1336 Class<?>[] parmTypes = method.getParameterTypes(); 1337 if (parmTypes.length == 3 && 1338 parmTypes[0] == ScriptRuntime.ScriptableClass && 1339 parmTypes[1] == FunctionObject.class && 1340 parmTypes[2] == ScriptRuntime.ScriptableClass && 1341 Modifier.isStatic(method.getModifiers())) 1342 { 1343 finishInit = method; 1344 continue; 1345 } 1346 } 1347 // ignore any compiler generated methods. 1348 if (name.indexOf('$') != -1) 1349 continue; 1350 if (name.equals(ctorName)) 1351 continue; 1352 1353 Annotation annotation = null; 1354 String prefix = null; 1355 if (method.isAnnotationPresent(JSFunction.class)) { 1356 annotation = method.getAnnotation(JSFunction.class); 1357 } else if (method.isAnnotationPresent(JSStaticFunction.class)) { 1358 annotation = method.getAnnotation(JSStaticFunction.class); 1359 } else if (method.isAnnotationPresent(JSGetter.class)) { 1360 annotation = method.getAnnotation(JSGetter.class); 1361 } else if (method.isAnnotationPresent(JSSetter.class)) { 1362 continue; 1363 } 1364 1365 if (annotation == null) { 1366 if (name.startsWith(functionPrefix)) { 1367 prefix = functionPrefix; 1368 } else if (name.startsWith(staticFunctionPrefix)) { 1369 prefix = staticFunctionPrefix; 1370 } else if (name.startsWith(getterPrefix)) { 1371 prefix = getterPrefix; 1372 } else if (annotation == null) { 1373 // note that setterPrefix is among the unhandled names here - 1374 // we deal with that when we see the getter 1375 continue; 1376 } 1377 } 1378 1379 boolean isStatic = annotation instanceof JSStaticFunction 1380 || prefix == staticFunctionPrefix; 1381 HashSet<String> names = isStatic ? staticNames : instanceNames; 1382 String propName = getPropertyName(name, prefix, annotation); 1383 if (names.contains(propName)) { 1384 throw Context.reportRuntimeError2("duplicate.defineClass.name", 1385 name, propName); 1386 } 1387 names.add(propName); 1388 name = propName; 1389 1390 if (annotation instanceof JSGetter || prefix == getterPrefix) { 1391 if (!(proto instanceof ScriptableObject)) { 1392 throw Context.reportRuntimeError2( 1393 "msg.extend.scriptable", 1394 proto.getClass().toString(), name); 1395 } 1396 Method setter = findSetterMethod(methods, name, setterPrefix); 1397 int attr = ScriptableObject.PERMANENT | 1398 ScriptableObject.DONTENUM | 1399 (setter != null ? 0 1400 : ScriptableObject.READONLY); 1401 ((ScriptableObject) proto).defineProperty(name, null, 1402 method, setter, 1403 attr); 1404 continue; 1405 } 1406 1407 if (isStatic && !Modifier.isStatic(method.getModifiers())) { 1408 throw Context.reportRuntimeError( 1409 "jsStaticFunction must be used with static method."); 1410 } 1411 1412 FunctionObject f = new FunctionObject(name, method, proto); 1413 if (f.isVarArgsConstructor()) { 1414 throw Context.reportRuntimeError1 1415 ("msg.varargs.fun", ctorMember.getName()); 1416 } 1417 defineProperty(isStatic ? ctor : proto, name, f, DONTENUM); 1418 if (sealed) { 1419 f.sealObject(); 1420 } 1421 } 1422 1423 // Call user code to complete initialization if necessary. 1424 if (finishInit != null) { 1425 Object[] finishArgs = { scope, ctor, proto }; 1426 finishInit.invoke(null, finishArgs); 1427 } 1428 1429 // Seal the object if necessary. 1430 if (sealed) { 1431 ctor.sealObject(); 1432 if (proto instanceof ScriptableObject) { 1433 ((ScriptableObject) proto).sealObject(); 1434 } 1435 } 1436 1437 return ctor; 1438 } 1439 findAnnotatedMember(AccessibleObject[] members, Class<? extends Annotation> annotation)1440 private static Member findAnnotatedMember(AccessibleObject[] members, 1441 Class<? extends Annotation> annotation) { 1442 for (AccessibleObject member : members) { 1443 if (member.isAnnotationPresent(annotation)) { 1444 return (Member) member; 1445 } 1446 } 1447 return null; 1448 } 1449 findSetterMethod(Method[] methods, String name, String prefix)1450 private static Method findSetterMethod(Method[] methods, 1451 String name, 1452 String prefix) { 1453 String newStyleName = "set" 1454 + Character.toUpperCase(name.charAt(0)) 1455 + name.substring(1); 1456 for (Method method : methods) { 1457 JSSetter annotation = method.getAnnotation(JSSetter.class); 1458 if (annotation != null) { 1459 if (name.equals(annotation.value()) || 1460 ("".equals(annotation.value()) && newStyleName.equals(method.getName()))) { 1461 return method; 1462 } 1463 } 1464 } 1465 String oldStyleName = prefix + name; 1466 for (Method method : methods) { 1467 if (oldStyleName.equals(method.getName())) { 1468 return method; 1469 } 1470 } 1471 return null; 1472 } 1473 getPropertyName(String methodName, String prefix, Annotation annotation)1474 private static String getPropertyName(String methodName, 1475 String prefix, 1476 Annotation annotation) { 1477 if (prefix != null) { 1478 return methodName.substring(prefix.length()); 1479 } 1480 String propName = null; 1481 if (annotation instanceof JSGetter) { 1482 propName = ((JSGetter) annotation).value(); 1483 if (propName == null || propName.length() == 0) { 1484 if (methodName.length() > 3 && methodName.startsWith("get")) { 1485 propName = methodName.substring(3); 1486 if (Character.isUpperCase(propName.charAt(0))) { 1487 if (propName.length() == 1) { 1488 propName = propName.toLowerCase(); 1489 } else if (!Character.isUpperCase(propName.charAt(1))){ 1490 propName = Character.toLowerCase(propName.charAt(0)) 1491 + propName.substring(1); 1492 } 1493 } 1494 } 1495 } 1496 } else if (annotation instanceof JSFunction) { 1497 propName = ((JSFunction) annotation).value(); 1498 } else if (annotation instanceof JSStaticFunction) { 1499 propName = ((JSStaticFunction) annotation).value(); 1500 } 1501 if (propName == null || propName.length() == 0) { 1502 propName = methodName; 1503 } 1504 return propName; 1505 } 1506 1507 @SuppressWarnings({"unchecked"}) extendsScriptable(Class<?> c)1508 private static <T extends Scriptable> Class<T> extendsScriptable(Class<?> c) 1509 { 1510 if (ScriptRuntime.ScriptableClass.isAssignableFrom(c)) 1511 return (Class<T>) c; 1512 return null; 1513 } 1514 1515 /** 1516 * Define a JavaScript property. 1517 * 1518 * Creates the property with an initial value and sets its attributes. 1519 * 1520 * @param propertyName the name of the property to define. 1521 * @param value the initial value of the property 1522 * @param attributes the attributes of the JavaScript property 1523 * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) 1524 */ defineProperty(String propertyName, Object value, int attributes)1525 public void defineProperty(String propertyName, Object value, 1526 int attributes) 1527 { 1528 checkNotSealed(propertyName, 0); 1529 put(propertyName, this, value); 1530 setAttributes(propertyName, attributes); 1531 } 1532 1533 /** 1534 * Utility method to add properties to arbitrary Scriptable object. 1535 * If destination is instance of ScriptableObject, calls 1536 * defineProperty there, otherwise calls put in destination 1537 * ignoring attributes 1538 */ defineProperty(Scriptable destination, String propertyName, Object value, int attributes)1539 public static void defineProperty(Scriptable destination, 1540 String propertyName, Object value, 1541 int attributes) 1542 { 1543 if (!(destination instanceof ScriptableObject)) { 1544 destination.put(propertyName, destination, value); 1545 return; 1546 } 1547 ScriptableObject so = (ScriptableObject)destination; 1548 so.defineProperty(propertyName, value, attributes); 1549 } 1550 1551 /** 1552 * Utility method to add properties to arbitrary Scriptable object. 1553 * If destination is instance of ScriptableObject, calls 1554 * defineProperty there, otherwise calls put in destination 1555 * ignoring attributes 1556 */ defineConstProperty(Scriptable destination, String propertyName)1557 public static void defineConstProperty(Scriptable destination, 1558 String propertyName) 1559 { 1560 if (destination instanceof ConstProperties) { 1561 ConstProperties cp = (ConstProperties)destination; 1562 cp.defineConst(propertyName, destination); 1563 } else 1564 defineProperty(destination, propertyName, Undefined.instance, CONST); 1565 } 1566 1567 /** 1568 * Define a JavaScript property with getter and setter side effects. 1569 * 1570 * If the setter is not found, the attribute READONLY is added to 1571 * the given attributes. <p> 1572 * 1573 * The getter must be a method with zero parameters, and the setter, if 1574 * found, must be a method with one parameter.<p> 1575 * 1576 * @param propertyName the name of the property to define. This name 1577 * also affects the name of the setter and getter 1578 * to search for. If the propertyId is "foo", then 1579 * <code>clazz</code> will be searched for "getFoo" 1580 * and "setFoo" methods. 1581 * @param clazz the Java class to search for the getter and setter 1582 * @param attributes the attributes of the JavaScript property 1583 * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) 1584 */ defineProperty(String propertyName, Class<?> clazz, int attributes)1585 public void defineProperty(String propertyName, Class<?> clazz, 1586 int attributes) 1587 { 1588 int length = propertyName.length(); 1589 if (length == 0) throw new IllegalArgumentException(); 1590 char[] buf = new char[3 + length]; 1591 propertyName.getChars(0, length, buf, 3); 1592 buf[3] = Character.toUpperCase(buf[3]); 1593 buf[0] = 'g'; 1594 buf[1] = 'e'; 1595 buf[2] = 't'; 1596 String getterName = new String(buf); 1597 buf[0] = 's'; 1598 String setterName = new String(buf); 1599 1600 Method[] methods = FunctionObject.getMethodList(clazz); 1601 Method getter = FunctionObject.findSingleMethod(methods, getterName); 1602 Method setter = FunctionObject.findSingleMethod(methods, setterName); 1603 if (setter == null) 1604 attributes |= ScriptableObject.READONLY; 1605 defineProperty(propertyName, null, getter, 1606 setter == null ? null : setter, attributes); 1607 } 1608 1609 /** 1610 * Define a JavaScript property. 1611 * 1612 * Use this method only if you wish to define getters and setters for 1613 * a given property in a ScriptableObject. To create a property without 1614 * special getter or setter side effects, use 1615 * <code>defineProperty(String,int)</code>. 1616 * 1617 * If <code>setter</code> is null, the attribute READONLY is added to 1618 * the given attributes.<p> 1619 * 1620 * Several forms of getters or setters are allowed. In all cases the 1621 * type of the value parameter can be any one of the following types: 1622 * Object, String, boolean, Scriptable, byte, short, int, long, float, 1623 * or double. The runtime will perform appropriate conversions based 1624 * upon the type of the parameter (see description in FunctionObject). 1625 * The first forms are nonstatic methods of the class referred to 1626 * by 'this': 1627 * <pre> 1628 * Object getFoo(); 1629 * void setFoo(SomeType value);</pre> 1630 * Next are static methods that may be of any class; the object whose 1631 * property is being accessed is passed in as an extra argument: 1632 * <pre> 1633 * static Object getFoo(Scriptable obj); 1634 * static void setFoo(Scriptable obj, SomeType value);</pre> 1635 * Finally, it is possible to delegate to another object entirely using 1636 * the <code>delegateTo</code> parameter. In this case the methods are 1637 * nonstatic methods of the class delegated to, and the object whose 1638 * property is being accessed is passed in as an extra argument: 1639 * <pre> 1640 * Object getFoo(Scriptable obj); 1641 * void setFoo(Scriptable obj, SomeType value);</pre> 1642 * 1643 * @param propertyName the name of the property to define. 1644 * @param delegateTo an object to call the getter and setter methods on, 1645 * or null, depending on the form used above. 1646 * @param getter the method to invoke to get the value of the property 1647 * @param setter the method to invoke to set the value of the property 1648 * @param attributes the attributes of the JavaScript property 1649 */ defineProperty(String propertyName, Object delegateTo, Method getter, Method setter, int attributes)1650 public void defineProperty(String propertyName, Object delegateTo, 1651 Method getter, Method setter, int attributes) 1652 { 1653 MemberBox getterBox = null; 1654 if (getter != null) { 1655 getterBox = new MemberBox(getter); 1656 1657 boolean delegatedForm; 1658 if (!Modifier.isStatic(getter.getModifiers())) { 1659 delegatedForm = (delegateTo != null); 1660 getterBox.delegateTo = delegateTo; 1661 } else { 1662 delegatedForm = true; 1663 // Ignore delegateTo for static getter but store 1664 // non-null delegateTo indicator. 1665 getterBox.delegateTo = Void.TYPE; 1666 } 1667 1668 String errorId = null; 1669 Class<?>[] parmTypes = getter.getParameterTypes(); 1670 if (parmTypes.length == 0) { 1671 if (delegatedForm) { 1672 errorId = "msg.obj.getter.parms"; 1673 } 1674 } else if (parmTypes.length == 1) { 1675 Object argType = parmTypes[0]; 1676 // Allow ScriptableObject for compatibility 1677 if (!(argType == ScriptRuntime.ScriptableClass || 1678 argType == ScriptRuntime.ScriptableObjectClass)) 1679 { 1680 errorId = "msg.bad.getter.parms"; 1681 } else if (!delegatedForm) { 1682 errorId = "msg.bad.getter.parms"; 1683 } 1684 } else { 1685 errorId = "msg.bad.getter.parms"; 1686 } 1687 if (errorId != null) { 1688 throw Context.reportRuntimeError1(errorId, getter.toString()); 1689 } 1690 } 1691 1692 MemberBox setterBox = null; 1693 if (setter != null) { 1694 if (setter.getReturnType() != Void.TYPE) 1695 throw Context.reportRuntimeError1("msg.setter.return", 1696 setter.toString()); 1697 1698 setterBox = new MemberBox(setter); 1699 1700 boolean delegatedForm; 1701 if (!Modifier.isStatic(setter.getModifiers())) { 1702 delegatedForm = (delegateTo != null); 1703 setterBox.delegateTo = delegateTo; 1704 } else { 1705 delegatedForm = true; 1706 // Ignore delegateTo for static setter but store 1707 // non-null delegateTo indicator. 1708 setterBox.delegateTo = Void.TYPE; 1709 } 1710 1711 String errorId = null; 1712 Class<?>[] parmTypes = setter.getParameterTypes(); 1713 if (parmTypes.length == 1) { 1714 if (delegatedForm) { 1715 errorId = "msg.setter2.expected"; 1716 } 1717 } else if (parmTypes.length == 2) { 1718 Object argType = parmTypes[0]; 1719 // Allow ScriptableObject for compatibility 1720 if (!(argType == ScriptRuntime.ScriptableClass || 1721 argType == ScriptRuntime.ScriptableObjectClass)) 1722 { 1723 errorId = "msg.setter2.parms"; 1724 } else if (!delegatedForm) { 1725 errorId = "msg.setter1.parms"; 1726 } 1727 } else { 1728 errorId = "msg.setter.parms"; 1729 } 1730 if (errorId != null) { 1731 throw Context.reportRuntimeError1(errorId, setter.toString()); 1732 } 1733 } 1734 1735 GetterSlot gslot = (GetterSlot)getSlot(propertyName, 0, 1736 SLOT_MODIFY_GETTER_SETTER); 1737 gslot.setAttributes(attributes); 1738 gslot.getter = getterBox; 1739 gslot.setter = setterBox; 1740 } 1741 1742 /** 1743 * Defines one or more properties on this object. 1744 * 1745 * @param cx the current Context 1746 * @param props a map of property ids to property descriptors 1747 */ defineOwnProperties(Context cx, ScriptableObject props)1748 public void defineOwnProperties(Context cx, ScriptableObject props) { 1749 Object[] ids = props.getIds(); 1750 for (Object id : ids) { 1751 Object descObj = props.get(id); 1752 ScriptableObject desc = ensureScriptableObject(descObj); 1753 checkPropertyDefinition(desc); 1754 } 1755 for (Object id : ids) { 1756 ScriptableObject desc = (ScriptableObject)props.get(id); 1757 defineOwnProperty(cx, id, desc); 1758 } 1759 } 1760 1761 /** 1762 * Defines a property on an object. 1763 * 1764 * @param cx the current Context 1765 * @param id the name/index of the property 1766 * @param desc the new property descriptor, as described in 8.6.1 1767 */ defineOwnProperty(Context cx, Object id, ScriptableObject desc)1768 public void defineOwnProperty(Context cx, Object id, ScriptableObject desc) { 1769 checkPropertyDefinition(desc); 1770 defineOwnProperty(cx, id, desc, true); 1771 } 1772 1773 /** 1774 * Defines a property on an object. 1775 * 1776 * Based on [[DefineOwnProperty]] from 8.12.10 of the spec. 1777 * 1778 * @param cx the current Context 1779 * @param id the name/index of the property 1780 * @param desc the new property descriptor, as described in 8.6.1 1781 * @param checkValid whether to perform validity checks 1782 */ defineOwnProperty(Context cx, Object id, ScriptableObject desc, boolean checkValid)1783 protected void defineOwnProperty(Context cx, Object id, ScriptableObject desc, 1784 boolean checkValid) { 1785 1786 Slot slot = getSlot(cx, id, SLOT_QUERY); 1787 boolean isNew = slot == null; 1788 1789 if (checkValid) { 1790 ScriptableObject current = slot == null ? 1791 null : slot.getPropertyDescriptor(cx, this); 1792 String name = ScriptRuntime.toString(id); 1793 checkPropertyChange(name, current, desc); 1794 } 1795 1796 boolean isAccessor = isAccessorDescriptor(desc); 1797 final int attributes; 1798 1799 if (slot == null) { // new slot 1800 slot = getSlot(cx, id, isAccessor ? SLOT_MODIFY_GETTER_SETTER : SLOT_MODIFY); 1801 attributes = applyDescriptorToAttributeBitset(DONTENUM|READONLY|PERMANENT, desc); 1802 } else { 1803 attributes = applyDescriptorToAttributeBitset(slot.getAttributes(), desc); 1804 } 1805 1806 slot = unwrapSlot(slot); 1807 1808 if (isAccessor) { 1809 if ( !(slot instanceof GetterSlot) ) { 1810 slot = getSlot(cx, id, SLOT_MODIFY_GETTER_SETTER); 1811 } 1812 1813 GetterSlot gslot = (GetterSlot) slot; 1814 1815 Object getter = getProperty(desc, "get"); 1816 if (getter != NOT_FOUND) { 1817 gslot.getter = getter; 1818 } 1819 Object setter = getProperty(desc, "set"); 1820 if (setter != NOT_FOUND) { 1821 gslot.setter = setter; 1822 } 1823 1824 gslot.value = Undefined.instance; 1825 gslot.setAttributes(attributes); 1826 } else { 1827 if (slot instanceof GetterSlot && isDataDescriptor(desc)) { 1828 slot = getSlot(cx, id, SLOT_CONVERT_ACCESSOR_TO_DATA); 1829 } 1830 1831 Object value = getProperty(desc, "value"); 1832 if (value != NOT_FOUND) { 1833 slot.value = value; 1834 } else if (isNew) { 1835 slot.value = Undefined.instance; 1836 } 1837 slot.setAttributes(attributes); 1838 } 1839 } 1840 checkPropertyDefinition(ScriptableObject desc)1841 protected void checkPropertyDefinition(ScriptableObject desc) { 1842 Object getter = getProperty(desc, "get"); 1843 if (getter != NOT_FOUND && getter != Undefined.instance 1844 && !(getter instanceof Callable)) { 1845 throw ScriptRuntime.notFunctionError(getter); 1846 } 1847 Object setter = getProperty(desc, "set"); 1848 if (setter != NOT_FOUND && setter != Undefined.instance 1849 && !(setter instanceof Callable)) { 1850 throw ScriptRuntime.notFunctionError(setter); 1851 } 1852 if (isDataDescriptor(desc) && isAccessorDescriptor(desc)) { 1853 throw ScriptRuntime.typeError0("msg.both.data.and.accessor.desc"); 1854 } 1855 } 1856 checkPropertyChange(String id, ScriptableObject current, ScriptableObject desc)1857 protected void checkPropertyChange(String id, ScriptableObject current, 1858 ScriptableObject desc) { 1859 if (current == null) { // new property 1860 if (!isExtensible()) throw ScriptRuntime.typeError0("msg.not.extensible"); 1861 } else { 1862 if (isFalse(current.get("configurable", current))) { 1863 if (isTrue(getProperty(desc, "configurable"))) 1864 throw ScriptRuntime.typeError1( 1865 "msg.change.configurable.false.to.true", id); 1866 if (isTrue(current.get("enumerable", current)) != isTrue(getProperty(desc, "enumerable"))) 1867 throw ScriptRuntime.typeError1( 1868 "msg.change.enumerable.with.configurable.false", id); 1869 boolean isData = isDataDescriptor(desc); 1870 boolean isAccessor = isAccessorDescriptor(desc); 1871 if (!isData && !isAccessor) { 1872 // no further validation required for generic descriptor 1873 } else if (isData && isDataDescriptor(current)) { 1874 if (isFalse(current.get("writable", current))) { 1875 if (isTrue(getProperty(desc, "writable"))) 1876 throw ScriptRuntime.typeError1( 1877 "msg.change.writable.false.to.true.with.configurable.false", id); 1878 1879 if (!sameValue(getProperty(desc, "value"), current.get("value", current))) 1880 throw ScriptRuntime.typeError1( 1881 "msg.change.value.with.writable.false", id); 1882 } 1883 } else if (isAccessor && isAccessorDescriptor(current)) { 1884 if (!sameValue(getProperty(desc, "set"), current.get("set", current))) 1885 throw ScriptRuntime.typeError1( 1886 "msg.change.setter.with.configurable.false", id); 1887 1888 if (!sameValue(getProperty(desc, "get"), current.get("get", current))) 1889 throw ScriptRuntime.typeError1( 1890 "msg.change.getter.with.configurable.false", id); 1891 } else { 1892 if (isDataDescriptor(current)) 1893 throw ScriptRuntime.typeError1( 1894 "msg.change.property.data.to.accessor.with.configurable.false", id); 1895 else 1896 throw ScriptRuntime.typeError1( 1897 "msg.change.property.accessor.to.data.with.configurable.false", id); 1898 } 1899 } 1900 } 1901 } 1902 isTrue(Object value)1903 protected static boolean isTrue(Object value) { 1904 return (value != NOT_FOUND) && ScriptRuntime.toBoolean(value); 1905 } 1906 isFalse(Object value)1907 protected static boolean isFalse(Object value) { 1908 return !isTrue(value); 1909 } 1910 1911 /** 1912 * Implements SameValue as described in ES5 9.12, additionally checking 1913 * if new value is defined. 1914 * @param newValue the new value 1915 * @param currentValue the current value 1916 * @return true if values are the same as defined by ES5 9.12 1917 */ sameValue(Object newValue, Object currentValue)1918 protected boolean sameValue(Object newValue, Object currentValue) { 1919 if (newValue == NOT_FOUND) { 1920 return true; 1921 } 1922 if (currentValue == NOT_FOUND) { 1923 currentValue = Undefined.instance; 1924 } 1925 // Special rules for numbers: NaN is considered the same value, 1926 // while zeroes with different signs are considered different. 1927 if (currentValue instanceof Number && newValue instanceof Number) { 1928 double d1 = ((Number)currentValue).doubleValue(); 1929 double d2 = ((Number)newValue).doubleValue(); 1930 if (Double.isNaN(d1) && Double.isNaN(d2)) { 1931 return true; 1932 } 1933 if (d1 == 0.0 && Double.doubleToLongBits(d1) != Double.doubleToLongBits(d2)) { 1934 return false; 1935 } 1936 } 1937 return ScriptRuntime.shallowEq(currentValue, newValue); 1938 } 1939 applyDescriptorToAttributeBitset(int attributes, ScriptableObject desc)1940 protected int applyDescriptorToAttributeBitset(int attributes, 1941 ScriptableObject desc) 1942 { 1943 Object enumerable = getProperty(desc, "enumerable"); 1944 if (enumerable != NOT_FOUND) { 1945 attributes = ScriptRuntime.toBoolean(enumerable) 1946 ? attributes & ~DONTENUM : attributes | DONTENUM; 1947 } 1948 1949 Object writable = getProperty(desc, "writable"); 1950 if (writable != NOT_FOUND) { 1951 attributes = ScriptRuntime.toBoolean(writable) 1952 ? attributes & ~READONLY : attributes | READONLY; 1953 } 1954 1955 Object configurable = getProperty(desc, "configurable"); 1956 if (configurable != NOT_FOUND) { 1957 attributes = ScriptRuntime.toBoolean(configurable) 1958 ? attributes & ~PERMANENT : attributes | PERMANENT; 1959 } 1960 1961 return attributes; 1962 } 1963 1964 /** 1965 * Implements IsDataDescriptor as described in ES5 8.10.2 1966 * @param desc a property descriptor 1967 * @return true if this is a data descriptor. 1968 */ isDataDescriptor(ScriptableObject desc)1969 protected boolean isDataDescriptor(ScriptableObject desc) { 1970 return hasProperty(desc, "value") || hasProperty(desc, "writable"); 1971 } 1972 1973 /** 1974 * Implements IsAccessorDescriptor as described in ES5 8.10.1 1975 * @param desc a property descriptor 1976 * @return true if this is an accessor descriptor. 1977 */ isAccessorDescriptor(ScriptableObject desc)1978 protected boolean isAccessorDescriptor(ScriptableObject desc) { 1979 return hasProperty(desc, "get") || hasProperty(desc, "set"); 1980 } 1981 1982 /** 1983 * Implements IsGenericDescriptor as described in ES5 8.10.3 1984 * @param desc a property descriptor 1985 * @return true if this is a generic descriptor. 1986 */ isGenericDescriptor(ScriptableObject desc)1987 protected boolean isGenericDescriptor(ScriptableObject desc) { 1988 return !isDataDescriptor(desc) && !isAccessorDescriptor(desc); 1989 } 1990 ensureScriptable(Object arg)1991 protected static Scriptable ensureScriptable(Object arg) { 1992 if ( !(arg instanceof Scriptable) ) 1993 throw ScriptRuntime.typeError1("msg.arg.not.object", ScriptRuntime.typeof(arg)); 1994 return (Scriptable) arg; 1995 } 1996 ensureScriptableObject(Object arg)1997 protected static ScriptableObject ensureScriptableObject(Object arg) { 1998 if ( !(arg instanceof ScriptableObject) ) 1999 throw ScriptRuntime.typeError1("msg.arg.not.object", ScriptRuntime.typeof(arg)); 2000 return (ScriptableObject) arg; 2001 } 2002 2003 /** 2004 * Search for names in a class, adding the resulting methods 2005 * as properties. 2006 * 2007 * <p> Uses reflection to find the methods of the given names. Then 2008 * FunctionObjects are constructed from the methods found, and 2009 * are added to this object as properties with the given names. 2010 * 2011 * @param names the names of the Methods to add as function properties 2012 * @param clazz the class to search for the Methods 2013 * @param attributes the attributes of the new properties 2014 * @see org.mozilla.javascript.FunctionObject 2015 */ defineFunctionProperties(String[] names, Class<?> clazz, int attributes)2016 public void defineFunctionProperties(String[] names, Class<?> clazz, 2017 int attributes) 2018 { 2019 Method[] methods = FunctionObject.getMethodList(clazz); 2020 for (int i=0; i < names.length; i++) { 2021 String name = names[i]; 2022 Method m = FunctionObject.findSingleMethod(methods, name); 2023 if (m == null) { 2024 throw Context.reportRuntimeError2( 2025 "msg.method.not.found", name, clazz.getName()); 2026 } 2027 FunctionObject f = new FunctionObject(name, m, this); 2028 defineProperty(name, f, attributes); 2029 } 2030 } 2031 2032 /** 2033 * Get the Object.prototype property. 2034 * See ECMA 15.2.4. 2035 */ getObjectPrototype(Scriptable scope)2036 public static Scriptable getObjectPrototype(Scriptable scope) { 2037 return TopLevel.getBuiltinPrototype(getTopLevelScope(scope), 2038 TopLevel.Builtins.Object); 2039 } 2040 2041 /** 2042 * Get the Function.prototype property. 2043 * See ECMA 15.3.4. 2044 */ getFunctionPrototype(Scriptable scope)2045 public static Scriptable getFunctionPrototype(Scriptable scope) { 2046 return TopLevel.getBuiltinPrototype(getTopLevelScope(scope), 2047 TopLevel.Builtins.Function); 2048 } 2049 getArrayPrototype(Scriptable scope)2050 public static Scriptable getArrayPrototype(Scriptable scope) { 2051 return TopLevel.getBuiltinPrototype(getTopLevelScope(scope), 2052 TopLevel.Builtins.Array); 2053 } 2054 2055 /** 2056 * Get the prototype for the named class. 2057 * 2058 * For example, <code>getClassPrototype(s, "Date")</code> will first 2059 * walk up the parent chain to find the outermost scope, then will 2060 * search that scope for the Date constructor, and then will 2061 * return Date.prototype. If any of the lookups fail, or 2062 * the prototype is not a JavaScript object, then null will 2063 * be returned. 2064 * 2065 * @param scope an object in the scope chain 2066 * @param className the name of the constructor 2067 * @return the prototype for the named class, or null if it 2068 * cannot be found. 2069 */ getClassPrototype(Scriptable scope, String className)2070 public static Scriptable getClassPrototype(Scriptable scope, 2071 String className) 2072 { 2073 scope = getTopLevelScope(scope); 2074 Object ctor = getProperty(scope, className); 2075 Object proto; 2076 if (ctor instanceof BaseFunction) { 2077 proto = ((BaseFunction)ctor).getPrototypeProperty(); 2078 } else if (ctor instanceof Scriptable) { 2079 Scriptable ctorObj = (Scriptable)ctor; 2080 proto = ctorObj.get("prototype", ctorObj); 2081 } else { 2082 return null; 2083 } 2084 if (proto instanceof Scriptable) { 2085 return (Scriptable)proto; 2086 } 2087 return null; 2088 } 2089 2090 /** 2091 * Get the global scope. 2092 * 2093 * <p>Walks the parent scope chain to find an object with a null 2094 * parent scope (the global object). 2095 * 2096 * @param obj a JavaScript object 2097 * @return the corresponding global scope 2098 */ getTopLevelScope(Scriptable obj)2099 public static Scriptable getTopLevelScope(Scriptable obj) 2100 { 2101 for (;;) { 2102 Scriptable parent = obj.getParentScope(); 2103 if (parent == null) { 2104 return obj; 2105 } 2106 obj = parent; 2107 } 2108 } 2109 isExtensible()2110 public boolean isExtensible() { 2111 return isExtensible; 2112 } 2113 preventExtensions()2114 public void preventExtensions() { 2115 isExtensible = false; 2116 } 2117 2118 /** 2119 * Seal this object. 2120 * 2121 * It is an error to add properties to or delete properties from 2122 * a sealed object. It is possible to change the value of an 2123 * existing property. Once an object is sealed it may not be unsealed. 2124 * 2125 * @since 1.4R3 2126 */ sealObject()2127 public synchronized void sealObject() { 2128 if (count >= 0) { 2129 // Make sure all LazilyLoadedCtors are initialized before sealing. 2130 Slot slot = firstAdded; 2131 while (slot != null) { 2132 Object value = slot.value; 2133 if (value instanceof LazilyLoadedCtor) { 2134 LazilyLoadedCtor initializer = (LazilyLoadedCtor) value; 2135 try { 2136 initializer.init(); 2137 } finally { 2138 slot.value = initializer.getValue(); 2139 } 2140 } 2141 slot = slot.orderedNext; 2142 } 2143 count = ~count; 2144 } 2145 } 2146 2147 /** 2148 * Return true if this object is sealed. 2149 * 2150 * @return true if sealed, false otherwise. 2151 * @since 1.4R3 2152 * @see #sealObject() 2153 */ isSealed()2154 public final boolean isSealed() { 2155 return count < 0; 2156 } 2157 checkNotSealed(String name, int index)2158 private void checkNotSealed(String name, int index) 2159 { 2160 if (!isSealed()) 2161 return; 2162 2163 String str = (name != null) ? name : Integer.toString(index); 2164 throw Context.reportRuntimeError1("msg.modify.sealed", str); 2165 } 2166 2167 /** 2168 * Gets a named property from an object or any object in its prototype chain. 2169 * <p> 2170 * Searches the prototype chain for a property named <code>name</code>. 2171 * <p> 2172 * @param obj a JavaScript object 2173 * @param name a property name 2174 * @return the value of a property with name <code>name</code> found in 2175 * <code>obj</code> or any object in its prototype chain, or 2176 * <code>Scriptable.NOT_FOUND</code> if not found 2177 * @since 1.5R2 2178 */ getProperty(Scriptable obj, String name)2179 public static Object getProperty(Scriptable obj, String name) 2180 { 2181 Scriptable start = obj; 2182 Object result; 2183 do { 2184 result = obj.get(name, start); 2185 if (result != Scriptable.NOT_FOUND) 2186 break; 2187 obj = obj.getPrototype(); 2188 } while (obj != null); 2189 return result; 2190 } 2191 2192 /** 2193 * Gets an indexed property from an object or any object in its prototype 2194 * chain and coerces it to the requested Java type. 2195 * <p> 2196 * Searches the prototype chain for a property with integral index 2197 * <code>index</code>. Note that if you wish to look for properties with numerical 2198 * but non-integral indicies, you should use getProperty(Scriptable,String) with 2199 * the string value of the index. 2200 * <p> 2201 * @param s a JavaScript object 2202 * @param index an integral index 2203 * @param type the required Java type of the result 2204 * @return the value of a property with name <code>name</code> found in 2205 * <code>obj</code> or any object in its prototype chain, or 2206 * null if not found. Note that it does not return 2207 * {@link Scriptable#NOT_FOUND} as it can ordinarily not be 2208 * converted to most of the types. 2209 * @since 1.7R3 2210 */ getTypedProperty(Scriptable s, int index, Class<T> type)2211 public static <T> T getTypedProperty(Scriptable s, int index, Class<T> type) { 2212 Object val = getProperty(s, index); 2213 if(val == Scriptable.NOT_FOUND) { 2214 val = null; 2215 } 2216 return type.cast(Context.jsToJava(val, type)); 2217 } 2218 2219 /** 2220 * Gets an indexed property from an object or any object in its prototype chain. 2221 * <p> 2222 * Searches the prototype chain for a property with integral index 2223 * <code>index</code>. Note that if you wish to look for properties with numerical 2224 * but non-integral indicies, you should use getProperty(Scriptable,String) with 2225 * the string value of the index. 2226 * <p> 2227 * @param obj a JavaScript object 2228 * @param index an integral index 2229 * @return the value of a property with index <code>index</code> found in 2230 * <code>obj</code> or any object in its prototype chain, or 2231 * <code>Scriptable.NOT_FOUND</code> if not found 2232 * @since 1.5R2 2233 */ getProperty(Scriptable obj, int index)2234 public static Object getProperty(Scriptable obj, int index) 2235 { 2236 Scriptable start = obj; 2237 Object result; 2238 do { 2239 result = obj.get(index, start); 2240 if (result != Scriptable.NOT_FOUND) 2241 break; 2242 obj = obj.getPrototype(); 2243 } while (obj != null); 2244 return result; 2245 } 2246 2247 /** 2248 * Gets a named property from an object or any object in its prototype chain 2249 * and coerces it to the requested Java type. 2250 * <p> 2251 * Searches the prototype chain for a property named <code>name</code>. 2252 * <p> 2253 * @param s a JavaScript object 2254 * @param name a property name 2255 * @param type the required Java type of the result 2256 * @return the value of a property with name <code>name</code> found in 2257 * <code>obj</code> or any object in its prototype chain, or 2258 * null if not found. Note that it does not return 2259 * {@link Scriptable#NOT_FOUND} as it can ordinarily not be 2260 * converted to most of the types. 2261 * @since 1.7R3 2262 */ getTypedProperty(Scriptable s, String name, Class<T> type)2263 public static <T> T getTypedProperty(Scriptable s, String name, Class<T> type) { 2264 Object val = getProperty(s, name); 2265 if(val == Scriptable.NOT_FOUND) { 2266 val = null; 2267 } 2268 return type.cast(Context.jsToJava(val, type)); 2269 } 2270 2271 /** 2272 * Returns whether a named property is defined in an object or any object 2273 * in its prototype chain. 2274 * <p> 2275 * Searches the prototype chain for a property named <code>name</code>. 2276 * <p> 2277 * @param obj a JavaScript object 2278 * @param name a property name 2279 * @return the true if property was found 2280 * @since 1.5R2 2281 */ hasProperty(Scriptable obj, String name)2282 public static boolean hasProperty(Scriptable obj, String name) 2283 { 2284 return null != getBase(obj, name); 2285 } 2286 2287 /** 2288 * If hasProperty(obj, name) would return true, then if the property that 2289 * was found is compatible with the new property, this method just returns. 2290 * If the property is not compatible, then an exception is thrown. 2291 * 2292 * A property redefinition is incompatible if the first definition was a 2293 * const declaration or if this one is. They are compatible only if neither 2294 * was const. 2295 */ redefineProperty(Scriptable obj, String name, boolean isConst)2296 public static void redefineProperty(Scriptable obj, String name, 2297 boolean isConst) 2298 { 2299 Scriptable base = getBase(obj, name); 2300 if (base == null) 2301 return; 2302 if (base instanceof ConstProperties) { 2303 ConstProperties cp = (ConstProperties)base; 2304 2305 if (cp.isConst(name)) 2306 throw Context.reportRuntimeError1("msg.const.redecl", name); 2307 } 2308 if (isConst) 2309 throw Context.reportRuntimeError1("msg.var.redecl", name); 2310 } 2311 /** 2312 * Returns whether an indexed property is defined in an object or any object 2313 * in its prototype chain. 2314 * <p> 2315 * Searches the prototype chain for a property with index <code>index</code>. 2316 * <p> 2317 * @param obj a JavaScript object 2318 * @param index a property index 2319 * @return the true if property was found 2320 * @since 1.5R2 2321 */ hasProperty(Scriptable obj, int index)2322 public static boolean hasProperty(Scriptable obj, int index) 2323 { 2324 return null != getBase(obj, index); 2325 } 2326 2327 /** 2328 * Puts a named property in an object or in an object in its prototype chain. 2329 * <p> 2330 * Searches for the named property in the prototype chain. If it is found, 2331 * the value of the property in <code>obj</code> is changed through a call 2332 * to {@link Scriptable#put(String, Scriptable, Object)} on the 2333 * prototype passing <code>obj</code> as the <code>start</code> argument. 2334 * This allows the prototype to veto the property setting in case the 2335 * prototype defines the property with [[ReadOnly]] attribute. If the 2336 * property is not found, it is added in <code>obj</code>. 2337 * @param obj a JavaScript object 2338 * @param name a property name 2339 * @param value any JavaScript value accepted by Scriptable.put 2340 * @since 1.5R2 2341 */ putProperty(Scriptable obj, String name, Object value)2342 public static void putProperty(Scriptable obj, String name, Object value) 2343 { 2344 Scriptable base = getBase(obj, name); 2345 if (base == null) 2346 base = obj; 2347 base.put(name, obj, value); 2348 } 2349 2350 /** 2351 * Puts a named property in an object or in an object in its prototype chain. 2352 * <p> 2353 * Searches for the named property in the prototype chain. If it is found, 2354 * the value of the property in <code>obj</code> is changed through a call 2355 * to {@link Scriptable#put(String, Scriptable, Object)} on the 2356 * prototype passing <code>obj</code> as the <code>start</code> argument. 2357 * This allows the prototype to veto the property setting in case the 2358 * prototype defines the property with [[ReadOnly]] attribute. If the 2359 * property is not found, it is added in <code>obj</code>. 2360 * @param obj a JavaScript object 2361 * @param name a property name 2362 * @param value any JavaScript value accepted by Scriptable.put 2363 * @since 1.5R2 2364 */ putConstProperty(Scriptable obj, String name, Object value)2365 public static void putConstProperty(Scriptable obj, String name, Object value) 2366 { 2367 Scriptable base = getBase(obj, name); 2368 if (base == null) 2369 base = obj; 2370 if (base instanceof ConstProperties) 2371 ((ConstProperties)base).putConst(name, obj, value); 2372 } 2373 2374 /** 2375 * Puts an indexed property in an object or in an object in its prototype chain. 2376 * <p> 2377 * Searches for the indexed property in the prototype chain. If it is found, 2378 * the value of the property in <code>obj</code> is changed through a call 2379 * to {@link Scriptable#put(int, Scriptable, Object)} on the prototype 2380 * passing <code>obj</code> as the <code>start</code> argument. This allows 2381 * the prototype to veto the property setting in case the prototype defines 2382 * the property with [[ReadOnly]] attribute. If the property is not found, 2383 * it is added in <code>obj</code>. 2384 * @param obj a JavaScript object 2385 * @param index a property index 2386 * @param value any JavaScript value accepted by Scriptable.put 2387 * @since 1.5R2 2388 */ putProperty(Scriptable obj, int index, Object value)2389 public static void putProperty(Scriptable obj, int index, Object value) 2390 { 2391 Scriptable base = getBase(obj, index); 2392 if (base == null) 2393 base = obj; 2394 base.put(index, obj, value); 2395 } 2396 2397 /** 2398 * Removes the property from an object or its prototype chain. 2399 * <p> 2400 * Searches for a property with <code>name</code> in obj or 2401 * its prototype chain. If it is found, the object's delete 2402 * method is called. 2403 * @param obj a JavaScript object 2404 * @param name a property name 2405 * @return true if the property doesn't exist or was successfully removed 2406 * @since 1.5R2 2407 */ deleteProperty(Scriptable obj, String name)2408 public static boolean deleteProperty(Scriptable obj, String name) 2409 { 2410 Scriptable base = getBase(obj, name); 2411 if (base == null) 2412 return true; 2413 base.delete(name); 2414 return !base.has(name, obj); 2415 } 2416 2417 /** 2418 * Removes the property from an object or its prototype chain. 2419 * <p> 2420 * Searches for a property with <code>index</code> in obj or 2421 * its prototype chain. If it is found, the object's delete 2422 * method is called. 2423 * @param obj a JavaScript object 2424 * @param index a property index 2425 * @return true if the property doesn't exist or was successfully removed 2426 * @since 1.5R2 2427 */ deleteProperty(Scriptable obj, int index)2428 public static boolean deleteProperty(Scriptable obj, int index) 2429 { 2430 Scriptable base = getBase(obj, index); 2431 if (base == null) 2432 return true; 2433 base.delete(index); 2434 return !base.has(index, obj); 2435 } 2436 2437 /** 2438 * Returns an array of all ids from an object and its prototypes. 2439 * <p> 2440 * @param obj a JavaScript object 2441 * @return an array of all ids from all object in the prototype chain. 2442 * If a given id occurs multiple times in the prototype chain, 2443 * it will occur only once in this list. 2444 * @since 1.5R2 2445 */ getPropertyIds(Scriptable obj)2446 public static Object[] getPropertyIds(Scriptable obj) 2447 { 2448 if (obj == null) { 2449 return ScriptRuntime.emptyArgs; 2450 } 2451 Object[] result = obj.getIds(); 2452 ObjToIntMap map = null; 2453 for (;;) { 2454 obj = obj.getPrototype(); 2455 if (obj == null) { 2456 break; 2457 } 2458 Object[] ids = obj.getIds(); 2459 if (ids.length == 0) { 2460 continue; 2461 } 2462 if (map == null) { 2463 if (result.length == 0) { 2464 result = ids; 2465 continue; 2466 } 2467 map = new ObjToIntMap(result.length + ids.length); 2468 for (int i = 0; i != result.length; ++i) { 2469 map.intern(result[i]); 2470 } 2471 result = null; // Allow to GC the result 2472 } 2473 for (int i = 0; i != ids.length; ++i) { 2474 map.intern(ids[i]); 2475 } 2476 } 2477 if (map != null) { 2478 result = map.getKeys(); 2479 } 2480 return result; 2481 } 2482 2483 /** 2484 * Call a method of an object. 2485 * @param obj the JavaScript object 2486 * @param methodName the name of the function property 2487 * @param args the arguments for the call 2488 * 2489 * @see Context#getCurrentContext() 2490 */ callMethod(Scriptable obj, String methodName, Object[] args)2491 public static Object callMethod(Scriptable obj, String methodName, 2492 Object[] args) 2493 { 2494 return callMethod(null, obj, methodName, args); 2495 } 2496 2497 /** 2498 * Call a method of an object. 2499 * @param cx the Context object associated with the current thread. 2500 * @param obj the JavaScript object 2501 * @param methodName the name of the function property 2502 * @param args the arguments for the call 2503 */ callMethod(Context cx, Scriptable obj, String methodName, Object[] args)2504 public static Object callMethod(Context cx, Scriptable obj, 2505 String methodName, 2506 Object[] args) 2507 { 2508 Object funObj = getProperty(obj, methodName); 2509 if (!(funObj instanceof Function)) { 2510 throw ScriptRuntime.notFunctionError(obj, methodName); 2511 } 2512 Function fun = (Function)funObj; 2513 // XXX: What should be the scope when calling funObj? 2514 // The following favor scope stored in the object on the assumption 2515 // that is more useful especially under dynamic scope setup. 2516 // An alternative is to check for dynamic scope flag 2517 // and use ScriptableObject.getTopLevelScope(fun) if the flag is not 2518 // set. But that require access to Context and messy code 2519 // so for now it is not checked. 2520 Scriptable scope = ScriptableObject.getTopLevelScope(obj); 2521 if (cx != null) { 2522 return fun.call(cx, scope, obj, args); 2523 } else { 2524 return Context.call(null, fun, scope, obj, args); 2525 } 2526 } 2527 getBase(Scriptable obj, String name)2528 private static Scriptable getBase(Scriptable obj, String name) 2529 { 2530 do { 2531 if (obj.has(name, obj)) 2532 break; 2533 obj = obj.getPrototype(); 2534 } while(obj != null); 2535 return obj; 2536 } 2537 getBase(Scriptable obj, int index)2538 private static Scriptable getBase(Scriptable obj, int index) 2539 { 2540 do { 2541 if (obj.has(index, obj)) 2542 break; 2543 obj = obj.getPrototype(); 2544 } while(obj != null); 2545 return obj; 2546 } 2547 2548 /** 2549 * Get arbitrary application-specific value associated with this object. 2550 * @param key key object to select particular value. 2551 * @see #associateValue(Object key, Object value) 2552 */ getAssociatedValue(Object key)2553 public final Object getAssociatedValue(Object key) 2554 { 2555 Map<Object,Object> h = associatedValues; 2556 if (h == null) 2557 return null; 2558 return h.get(key); 2559 } 2560 2561 /** 2562 * Get arbitrary application-specific value associated with the top scope 2563 * of the given scope. 2564 * The method first calls {@link #getTopLevelScope(Scriptable scope)} 2565 * and then searches the prototype chain of the top scope for the first 2566 * object containing the associated value with the given key. 2567 * 2568 * @param scope the starting scope. 2569 * @param key key object to select particular value. 2570 * @see #getAssociatedValue(Object key) 2571 */ getTopScopeValue(Scriptable scope, Object key)2572 public static Object getTopScopeValue(Scriptable scope, Object key) 2573 { 2574 scope = ScriptableObject.getTopLevelScope(scope); 2575 for (;;) { 2576 if (scope instanceof ScriptableObject) { 2577 ScriptableObject so = (ScriptableObject)scope; 2578 Object value = so.getAssociatedValue(key); 2579 if (value != null) { 2580 return value; 2581 } 2582 } 2583 scope = scope.getPrototype(); 2584 if (scope == null) { 2585 return null; 2586 } 2587 } 2588 } 2589 2590 /** 2591 * Associate arbitrary application-specific value with this object. 2592 * Value can only be associated with the given object and key only once. 2593 * The method ignores any subsequent attempts to change the already 2594 * associated value. 2595 * <p> The associated values are not serialized. 2596 * @param key key object to select particular value. 2597 * @param value the value to associate 2598 * @return the passed value if the method is called first time for the 2599 * given key or old value for any subsequent calls. 2600 * @see #getAssociatedValue(Object key) 2601 */ associateValue(Object key, Object value)2602 public synchronized final Object associateValue(Object key, Object value) 2603 { 2604 if (value == null) throw new IllegalArgumentException(); 2605 Map<Object,Object> h = associatedValues; 2606 if (h == null) { 2607 h = new HashMap<Object,Object>(); 2608 associatedValues = h; 2609 } 2610 return Kit.initHash(h, key, value); 2611 } 2612 2613 /** 2614 * 2615 * @param name 2616 * @param index 2617 * @param start 2618 * @param value 2619 * @return false if this != start and no slot was found. true if this == start 2620 * or this != start and a READONLY slot was found. 2621 */ putImpl(String name, int index, Scriptable start, Object value)2622 private boolean putImpl(String name, int index, Scriptable start, 2623 Object value) 2624 { 2625 // This method is very hot (basically called on each assignment) 2626 // so we inline the extensible/sealed checks below. 2627 Slot slot; 2628 if (this != start) { 2629 slot = getSlot(name, index, SLOT_QUERY); 2630 if (slot == null) { 2631 return false; 2632 } 2633 } else if (!isExtensible) { 2634 slot = getSlot(name, index, SLOT_QUERY); 2635 if (slot == null) { 2636 return true; 2637 } 2638 } else { 2639 if (count < 0) checkNotSealed(name, index); 2640 slot = getSlot(name, index, SLOT_MODIFY); 2641 } 2642 return slot.setValue(value, this, start); 2643 } 2644 2645 2646 /** 2647 * 2648 * @param name 2649 * @param index 2650 * @param start 2651 * @param value 2652 * @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means 2653 * defineConstProperty. READONLY means const initialization expression. 2654 * @return false if this != start and no slot was found. true if this == start 2655 * or this != start and a READONLY slot was found. 2656 */ putConstImpl(String name, int index, Scriptable start, Object value, int constFlag)2657 private boolean putConstImpl(String name, int index, Scriptable start, 2658 Object value, int constFlag) 2659 { 2660 assert (constFlag != EMPTY); 2661 Slot slot; 2662 if (this != start) { 2663 slot = getSlot(name, index, SLOT_QUERY); 2664 if (slot == null) { 2665 return false; 2666 } 2667 } else if (!isExtensible()) { 2668 slot = getSlot(name, index, SLOT_QUERY); 2669 if (slot == null) { 2670 return true; 2671 } 2672 } else { 2673 checkNotSealed(name, index); 2674 // either const hoisted declaration or initialization 2675 slot = unwrapSlot(getSlot(name, index, SLOT_MODIFY_CONST)); 2676 int attr = slot.getAttributes(); 2677 if ((attr & READONLY) == 0) 2678 throw Context.reportRuntimeError1("msg.var.redecl", name); 2679 if ((attr & UNINITIALIZED_CONST) != 0) { 2680 slot.value = value; 2681 // clear the bit on const initialization 2682 if (constFlag != UNINITIALIZED_CONST) 2683 slot.setAttributes(attr & ~UNINITIALIZED_CONST); 2684 } 2685 return true; 2686 } 2687 return slot.setValue(value, this, start); 2688 } 2689 findAttributeSlot(String name, int index, int accessType)2690 private Slot findAttributeSlot(String name, int index, int accessType) 2691 { 2692 Slot slot = getSlot(name, index, accessType); 2693 if (slot == null) { 2694 String str = (name != null ? name : Integer.toString(index)); 2695 throw Context.reportRuntimeError1("msg.prop.not.found", str); 2696 } 2697 return slot; 2698 } 2699 unwrapSlot(Slot slot)2700 private static Slot unwrapSlot(Slot slot) { 2701 return (slot instanceof RelinkedSlot) ? ((RelinkedSlot)slot).slot : slot; 2702 } 2703 2704 /** 2705 * Locate the slot with given name or index. Depending on the accessType 2706 * parameter and the current slot status, a new slot may be allocated. 2707 * 2708 * @param name property name or null if slot holds spare array index. 2709 * @param index index or 0 if slot holds property name. 2710 */ getSlot(String name, int index, int accessType)2711 private Slot getSlot(String name, int index, int accessType) 2712 { 2713 // Check the hashtable without using synchronization 2714 Slot[] slotsLocalRef = slots; // Get stable local reference 2715 if (slotsLocalRef == null && accessType == SLOT_QUERY) { 2716 return null; 2717 } 2718 2719 int indexOrHash = (name != null ? name.hashCode() : index); 2720 if (slotsLocalRef != null) { 2721 Slot slot; 2722 int slotIndex = getSlotIndex(slotsLocalRef.length, indexOrHash); 2723 for (slot = slotsLocalRef[slotIndex]; 2724 slot != null; 2725 slot = slot.next) { 2726 Object sname = slot.name; 2727 if (indexOrHash == slot.indexOrHash && 2728 (sname == name || 2729 (name != null && name.equals(sname)))) { 2730 break; 2731 } 2732 } 2733 switch (accessType) { 2734 case SLOT_QUERY: 2735 return slot; 2736 case SLOT_MODIFY: 2737 case SLOT_MODIFY_CONST: 2738 if (slot != null) 2739 return slot; 2740 break; 2741 case SLOT_MODIFY_GETTER_SETTER: 2742 slot = unwrapSlot(slot); 2743 if (slot instanceof GetterSlot) 2744 return slot; 2745 break; 2746 case SLOT_CONVERT_ACCESSOR_TO_DATA: 2747 slot = unwrapSlot(slot); 2748 if ( !(slot instanceof GetterSlot) ) 2749 return slot; 2750 break; 2751 } 2752 } 2753 2754 // A new slot has to be inserted or the old has to be replaced 2755 // by GetterSlot. Time to synchronize. 2756 return createSlot(name, indexOrHash, accessType); 2757 } 2758 createSlot(String name, int indexOrHash, int accessType)2759 private synchronized Slot createSlot(String name, int indexOrHash, int accessType) { 2760 Slot[] slotsLocalRef = slots; 2761 int insertPos; 2762 if (count == 0) { 2763 // Always throw away old slots if any on empty insert. 2764 slotsLocalRef = new Slot[INITIAL_SLOT_SIZE]; 2765 slots = slotsLocalRef; 2766 insertPos = getSlotIndex(slotsLocalRef.length, indexOrHash); 2767 } else { 2768 int tableSize = slotsLocalRef.length; 2769 insertPos = getSlotIndex(tableSize, indexOrHash); 2770 Slot prev = slotsLocalRef[insertPos]; 2771 Slot slot = prev; 2772 while (slot != null) { 2773 if (slot.indexOrHash == indexOrHash && 2774 (slot.name == name || 2775 (name != null && name.equals(slot.name)))) 2776 { 2777 break; 2778 } 2779 prev = slot; 2780 slot = slot.next; 2781 } 2782 2783 if (slot != null) { 2784 // A slot with same name/index already exists. This means that 2785 // a slot is being redefined from a value to a getter slot or 2786 // vice versa, or it could be a race in application code. 2787 // Check if we need to replace the slot depending on the 2788 // accessType flag and return the appropriate slot instance. 2789 2790 Slot inner = unwrapSlot(slot); 2791 Slot newSlot; 2792 2793 if (accessType == SLOT_MODIFY_GETTER_SETTER 2794 && !(inner instanceof GetterSlot)) { 2795 newSlot = new GetterSlot(name, indexOrHash, inner.getAttributes()); 2796 } else if (accessType == SLOT_CONVERT_ACCESSOR_TO_DATA 2797 && (inner instanceof GetterSlot)) { 2798 newSlot = new Slot(name, indexOrHash, inner.getAttributes()); 2799 } else if (accessType == SLOT_MODIFY_CONST) { 2800 return null; 2801 } else { 2802 return inner; 2803 } 2804 2805 newSlot.value = inner.value; 2806 newSlot.next = slot.next; 2807 // add new slot to linked list 2808 if (lastAdded != null) { 2809 lastAdded.orderedNext = newSlot; 2810 } 2811 if (firstAdded == null) { 2812 firstAdded = newSlot; 2813 } 2814 lastAdded = newSlot; 2815 // add new slot to hash table 2816 if (prev == slot) { 2817 slotsLocalRef[insertPos] = newSlot; 2818 } else { 2819 prev.next = newSlot; 2820 } 2821 // other housekeeping 2822 slot.markDeleted(); 2823 return newSlot; 2824 } else { 2825 // Check if the table is not too full before inserting. 2826 if (4 * (count + 1) > 3 * slotsLocalRef.length) { 2827 // table size must be a power of 2, always grow by x2 2828 slotsLocalRef = new Slot[slotsLocalRef.length * 2]; 2829 copyTable(slots, slotsLocalRef, count); 2830 slots = slotsLocalRef; 2831 insertPos = getSlotIndex(slotsLocalRef.length, 2832 indexOrHash); 2833 } 2834 } 2835 } 2836 Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER 2837 ? new GetterSlot(name, indexOrHash, 0) 2838 : new Slot(name, indexOrHash, 0)); 2839 if (accessType == SLOT_MODIFY_CONST) 2840 newSlot.setAttributes(CONST); 2841 ++count; 2842 // add new slot to linked list 2843 if (lastAdded != null) 2844 lastAdded.orderedNext = newSlot; 2845 if (firstAdded == null) 2846 firstAdded = newSlot; 2847 lastAdded = newSlot; 2848 // add new slot to hash table, return it 2849 addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos); 2850 return newSlot; 2851 } 2852 removeSlot(String name, int index)2853 private synchronized void removeSlot(String name, int index) { 2854 int indexOrHash = (name != null ? name.hashCode() : index); 2855 2856 Slot[] slotsLocalRef = slots; 2857 if (count != 0) { 2858 int tableSize = slotsLocalRef.length; 2859 int slotIndex = getSlotIndex(tableSize, indexOrHash); 2860 Slot prev = slotsLocalRef[slotIndex]; 2861 Slot slot = prev; 2862 while (slot != null) { 2863 if (slot.indexOrHash == indexOrHash && 2864 (slot.name == name || 2865 (name != null && name.equals(slot.name)))) 2866 { 2867 break; 2868 } 2869 prev = slot; 2870 slot = slot.next; 2871 } 2872 if (slot != null && (slot.getAttributes() & PERMANENT) == 0) { 2873 count--; 2874 // remove slot from hash table 2875 if (prev == slot) { 2876 slotsLocalRef[slotIndex] = slot.next; 2877 } else { 2878 prev.next = slot.next; 2879 } 2880 2881 // remove from ordered list. Previously this was done lazily in 2882 // getIds() but delete is an infrequent operation so O(n) 2883 // should be ok 2884 2885 // ordered list always uses the actual slot 2886 Slot deleted = unwrapSlot(slot); 2887 if (deleted == firstAdded) { 2888 prev = null; 2889 firstAdded = deleted.orderedNext; 2890 } else { 2891 prev = firstAdded; 2892 while (prev.orderedNext != deleted) { 2893 prev = prev.orderedNext; 2894 } 2895 prev.orderedNext = deleted.orderedNext; 2896 } 2897 if (deleted == lastAdded) { 2898 lastAdded = prev; 2899 } 2900 2901 // Mark the slot as removed. 2902 slot.markDeleted(); 2903 } 2904 } 2905 } 2906 getSlotIndex(int tableSize, int indexOrHash)2907 private static int getSlotIndex(int tableSize, int indexOrHash) 2908 { 2909 // tableSize is a power of 2 2910 return indexOrHash & (tableSize - 1); 2911 } 2912 2913 // Must be inside synchronized (this) copyTable(Slot[] oldSlots, Slot[] newSlots, int count)2914 private static void copyTable(Slot[] oldSlots, Slot[] newSlots, int count) 2915 { 2916 if (count == 0) throw Kit.codeBug(); 2917 2918 int tableSize = newSlots.length; 2919 int i = oldSlots.length; 2920 for (;;) { 2921 --i; 2922 Slot slot = oldSlots[i]; 2923 while (slot != null) { 2924 int insertPos = getSlotIndex(tableSize, slot.indexOrHash); 2925 // If slot has next chain in old table use a new 2926 // RelinkedSlot wrapper to keep old table valid 2927 Slot insSlot = slot.next == null ? slot : new RelinkedSlot(slot); 2928 addKnownAbsentSlot(newSlots, insSlot, insertPos); 2929 slot = slot.next; 2930 if (--count == 0) 2931 return; 2932 } 2933 } 2934 } 2935 2936 /** 2937 * Add slot with keys that are known to absent from the table. 2938 * This is an optimization to use when inserting into empty table, 2939 * after table growth or during deserialization. 2940 */ addKnownAbsentSlot(Slot[] slots, Slot slot, int insertPos)2941 private static void addKnownAbsentSlot(Slot[] slots, Slot slot, 2942 int insertPos) 2943 { 2944 if (slots[insertPos] == null) { 2945 slots[insertPos] = slot; 2946 } else { 2947 Slot prev = slots[insertPos]; 2948 Slot next = prev.next; 2949 while (next != null) { 2950 prev = next; 2951 next = prev.next; 2952 } 2953 prev.next = slot; 2954 } 2955 } 2956 getIds(boolean getAll)2957 Object[] getIds(boolean getAll) { 2958 Slot[] s = slots; 2959 Object[] a = ScriptRuntime.emptyArgs; 2960 if (s == null) 2961 return a; 2962 int c = 0; 2963 Slot slot = firstAdded; 2964 while (slot != null && slot.wasDeleted) { 2965 // we used to removed deleted slots from the linked list here 2966 // but this is now done in removeSlot(). There may still be deleted 2967 // slots (e.g. from slot conversion) but we don't want to mess 2968 // with the list in unsynchronized code. 2969 slot = slot.orderedNext; 2970 } 2971 while (slot != null) { 2972 if (getAll || (slot.getAttributes() & DONTENUM) == 0) { 2973 if (c == 0) 2974 a = new Object[s.length]; 2975 a[c++] = slot.name != null 2976 ? slot.name 2977 : Integer.valueOf(slot.indexOrHash); 2978 } 2979 slot = slot.orderedNext; 2980 while (slot != null && slot.wasDeleted) { 2981 // skip deleted slots, see comment above 2982 slot = slot.orderedNext; 2983 } 2984 } 2985 if (c == a.length) 2986 return a; 2987 Object[] result = new Object[c]; 2988 System.arraycopy(a, 0, result, 0, c); 2989 return result; 2990 } 2991 writeObject(ObjectOutputStream out)2992 private synchronized void writeObject(ObjectOutputStream out) 2993 throws IOException 2994 { 2995 out.defaultWriteObject(); 2996 int objectsCount = count; 2997 if (objectsCount < 0) { 2998 // "this" was sealed 2999 objectsCount = ~objectsCount; 3000 } 3001 if (objectsCount == 0) { 3002 out.writeInt(0); 3003 } else { 3004 out.writeInt(slots.length); 3005 Slot slot = firstAdded; 3006 while (slot != null && slot.wasDeleted) { 3007 // as long as we're traversing the order-added linked list, 3008 // remove deleted slots 3009 slot = slot.orderedNext; 3010 } 3011 firstAdded = slot; 3012 while (slot != null) { 3013 out.writeObject(slot); 3014 Slot next = slot.orderedNext; 3015 while (next != null && next.wasDeleted) { 3016 // remove deleted slots 3017 next = next.orderedNext; 3018 } 3019 slot.orderedNext = next; 3020 slot = next; 3021 } 3022 } 3023 } 3024 readObject(ObjectInputStream in)3025 private void readObject(ObjectInputStream in) 3026 throws IOException, ClassNotFoundException 3027 { 3028 in.defaultReadObject(); 3029 3030 int tableSize = in.readInt(); 3031 if (tableSize != 0) { 3032 // If tableSize is not a power of 2 find the closest 3033 // power of 2 >= the original size. 3034 if ((tableSize & (tableSize - 1)) != 0) { 3035 if (tableSize > 1 << 30) 3036 throw new RuntimeException("Property table overflow"); 3037 int newSize = INITIAL_SLOT_SIZE; 3038 while (newSize < tableSize) 3039 newSize <<= 1; 3040 tableSize = newSize; 3041 } 3042 slots = new Slot[tableSize]; 3043 int objectsCount = count; 3044 if (objectsCount < 0) { 3045 // "this" was sealed 3046 objectsCount = ~objectsCount; 3047 } 3048 Slot prev = null; 3049 for (int i=0; i != objectsCount; ++i) { 3050 lastAdded = (Slot)in.readObject(); 3051 if (i==0) { 3052 firstAdded = lastAdded; 3053 } else { 3054 prev.orderedNext = lastAdded; 3055 } 3056 int slotIndex = getSlotIndex(tableSize, lastAdded.indexOrHash); 3057 addKnownAbsentSlot(slots, lastAdded, slotIndex); 3058 prev = lastAdded; 3059 } 3060 } 3061 } 3062 getOwnPropertyDescriptor(Context cx, Object id)3063 protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) { 3064 Slot slot = getSlot(cx, id, SLOT_QUERY); 3065 if (slot == null) return null; 3066 Scriptable scope = getParentScope(); 3067 return slot.getPropertyDescriptor(cx, (scope == null ? this : scope)); 3068 } 3069 getSlot(Context cx, Object id, int accessType)3070 protected Slot getSlot(Context cx, Object id, int accessType) { 3071 String name = ScriptRuntime.toStringIdOrIndex(cx, id); 3072 if (name == null) { 3073 return getSlot(null, ScriptRuntime.lastIndexResult(cx), accessType); 3074 } else { 3075 return getSlot(name, 0, accessType); 3076 } 3077 } 3078 3079 // Partial implementation of java.util.Map. See NativeObject for 3080 // a subclass that implements java.util.Map. 3081 size()3082 public int size() { 3083 return count < 0 ? ~count : count; 3084 } 3085 isEmpty()3086 public boolean isEmpty() { 3087 return count == 0 || count == -1; 3088 } 3089 3090 get(Object key)3091 public Object get(Object key) { 3092 Object value = null; 3093 if (key instanceof String) { 3094 value = get((String) key, this); 3095 } else if (key instanceof Number) { 3096 value = get(((Number) key).intValue(), this); 3097 } 3098 if (value == Scriptable.NOT_FOUND || value == Undefined.instance) { 3099 return null; 3100 } else if (value instanceof Wrapper) { 3101 return ((Wrapper) value).unwrap(); 3102 } else { 3103 return value; 3104 } 3105 } 3106 3107 } 3108