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