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 package org.mozilla.javascript;
8 
9 /**
10  * This class implements the "arguments" object.
11  *
12  * See ECMA 10.1.8
13  *
14  * @see org.mozilla.javascript.NativeCall
15  * @author Norris Boyd
16  */
17 final class Arguments extends IdScriptableObject
18 {
19     static final long serialVersionUID = 4275508002492040609L;
20 
21     private static final String FTAG = "Arguments";
22 
Arguments(NativeCall activation)23     public Arguments(NativeCall activation)
24     {
25         this.activation = activation;
26 
27         Scriptable parent = activation.getParentScope();
28         setParentScope(parent);
29         setPrototype(ScriptableObject.getObjectPrototype(parent));
30 
31         args = activation.originalArgs;
32         lengthObj = Integer.valueOf(args.length);
33 
34         NativeFunction f = activation.function;
35         calleeObj = f;
36 
37         Scriptable topLevel = getTopLevelScope(parent);
38         constructor = getProperty(topLevel, "Object");
39 
40         int version = f.getLanguageVersion();
41         if (version <= Context.VERSION_1_3
42             && version != Context.VERSION_DEFAULT)
43         {
44             callerObj = null;
45         } else {
46             callerObj = NOT_FOUND;
47         }
48     }
49 
50     @Override
getClassName()51     public String getClassName()
52     {
53         return FTAG;
54     }
55 
arg(int index)56     private Object arg(int index) {
57       if (index < 0 || args.length <= index) return NOT_FOUND;
58       return args[index];
59     }
60 
61     // the following helper methods assume that 0 < index < args.length
62 
putIntoActivation(int index, Object value)63     private void putIntoActivation(int index, Object value) {
64         String argName = activation.function.getParamOrVarName(index);
65         activation.put(argName, activation, value);
66     }
67 
getFromActivation(int index)68     private Object getFromActivation(int index) {
69         String argName = activation.function.getParamOrVarName(index);
70         return activation.get(argName, activation);
71     }
72 
replaceArg(int index, Object value)73     private void replaceArg(int index, Object value) {
74       if (sharedWithActivation(index)) {
75         putIntoActivation(index, value);
76       }
77       synchronized (this) {
78         if (args == activation.originalArgs) {
79           args = args.clone();
80         }
81         args[index] = value;
82       }
83     }
84 
removeArg(int index)85     private void removeArg(int index) {
86       synchronized (this) {
87         if (args[index] != NOT_FOUND) {
88           if (args == activation.originalArgs) {
89             args = args.clone();
90           }
91           args[index] = NOT_FOUND;
92         }
93       }
94     }
95 
96     // end helpers
97 
98     @Override
has(int index, Scriptable start)99     public boolean has(int index, Scriptable start)
100     {
101         if (arg(index) != NOT_FOUND) {
102           return true;
103         }
104         return super.has(index, start);
105     }
106 
107     @Override
get(int index, Scriptable start)108     public Object get(int index, Scriptable start)
109     {
110       final Object value = arg(index);
111       if (value == NOT_FOUND) {
112         return super.get(index, start);
113       } else {
114         if (sharedWithActivation(index)) {
115           return getFromActivation(index);
116         } else {
117           return value;
118         }
119       }
120     }
121 
sharedWithActivation(int index)122     private boolean sharedWithActivation(int index)
123     {
124         NativeFunction f = activation.function;
125         int definedCount = f.getParamCount();
126         if (index < definedCount) {
127             // Check if argument is not hidden by later argument with the same
128             // name as hidden arguments are not shared with activation
129             if (index < definedCount - 1) {
130                 String argName = f.getParamOrVarName(index);
131                 for (int i = index + 1; i < definedCount; i++) {
132                     if (argName.equals(f.getParamOrVarName(i))) {
133                         return false;
134                     }
135                 }
136             }
137             return true;
138         }
139         return false;
140     }
141 
142     @Override
put(int index, Scriptable start, Object value)143     public void put(int index, Scriptable start, Object value)
144     {
145         if (arg(index) == NOT_FOUND) {
146           super.put(index, start, value);
147         } else {
148           replaceArg(index, value);
149         }
150     }
151 
152     @Override
delete(int index)153     public void delete(int index)
154     {
155         if (0 <= index && index < args.length) {
156           removeArg(index);
157         }
158         super.delete(index);
159     }
160 
161 // #string_id_map#
162 
163     private static final int
164         Id_callee           = 1,
165         Id_length           = 2,
166         Id_caller           = 3,
167         Id_constructor      = 4,
168 
169         MAX_INSTANCE_ID     = Id_constructor;
170 
171     @Override
getMaxInstanceId()172     protected int getMaxInstanceId()
173     {
174         return MAX_INSTANCE_ID;
175     }
176 
177     @Override
findInstanceIdInfo(String s)178     protected int findInstanceIdInfo(String s)
179     {
180         int id;
181 // #generated# Last update: 2010-01-06 05:48:21 ARST
182         L0: { id = 0; String X = null; int c;
183             int s_length = s.length();
184             if (s_length==6) {
185                 c=s.charAt(5);
186                 if (c=='e') { X="callee";id=Id_callee; }
187                 else if (c=='h') { X="length";id=Id_length; }
188                 else if (c=='r') { X="caller";id=Id_caller; }
189             }
190             else if (s_length==11) { X="constructor";id=Id_constructor; }
191             if (X!=null && X!=s && !X.equals(s)) id = 0;
192             break L0;
193         }
194 // #/generated#
195 
196         if (id == 0) return super.findInstanceIdInfo(s);
197 
198         int attr;
199         switch (id) {
200           case Id_callee:
201           case Id_caller:
202           case Id_length:
203           case Id_constructor:
204             attr = DONTENUM;
205             break;
206           default: throw new IllegalStateException();
207         }
208         return instanceIdInfo(attr, id);
209     }
210 
211 // #/string_id_map#
212 
213     @Override
getInstanceIdName(int id)214     protected String getInstanceIdName(int id)
215     {
216         switch (id) {
217             case Id_callee: return "callee";
218             case Id_length: return "length";
219             case Id_caller: return "caller";
220             case Id_constructor: return "constructor";
221         }
222         return null;
223     }
224 
225     @Override
getInstanceIdValue(int id)226     protected Object getInstanceIdValue(int id)
227     {
228         switch (id) {
229             case Id_callee: return calleeObj;
230             case Id_length: return lengthObj;
231             case Id_caller: {
232                 Object value = callerObj;
233                 if (value == UniqueTag.NULL_VALUE) { value = null; }
234                 else if (value == null) {
235                     NativeCall caller = activation.parentActivationCall;
236                     if (caller != null) {
237                         value = caller.get("arguments", caller);
238                     }
239                 }
240                 return value;
241             }
242             case Id_constructor:
243                 return constructor;
244         }
245         return super.getInstanceIdValue(id);
246     }
247 
248     @Override
setInstanceIdValue(int id, Object value)249     protected void setInstanceIdValue(int id, Object value)
250     {
251         switch (id) {
252             case Id_callee: calleeObj = value; return;
253             case Id_length: lengthObj = value; return;
254             case Id_caller:
255                 callerObj = (value != null) ? value : UniqueTag.NULL_VALUE;
256                 return;
257             case Id_constructor: constructor = value; return;
258         }
259         super.setInstanceIdValue(id, value);
260     }
261 
262     @Override
getIds(boolean getAll)263     Object[] getIds(boolean getAll)
264     {
265         Object[] ids = super.getIds(getAll);
266         if (args.length != 0) {
267             boolean[] present = new boolean[args.length];
268             int extraCount = args.length;
269             for (int i = 0; i != ids.length; ++i) {
270                 Object id = ids[i];
271                 if (id instanceof Integer) {
272                     int index = ((Integer)id).intValue();
273                     if (0 <= index && index < args.length) {
274                         if (!present[index]) {
275                             present[index] = true;
276                             extraCount--;
277                         }
278                     }
279                 }
280             }
281             if (!getAll) { // avoid adding args which were redefined to non-enumerable
282               for (int i = 0; i < present.length; i++) {
283                 if (!present[i] && super.has(i, this)) {
284                   present[i] = true;
285                   extraCount--;
286                 }
287               }
288             }
289             if (extraCount != 0) {
290                 Object[] tmp = new Object[extraCount + ids.length];
291                 System.arraycopy(ids, 0, tmp, extraCount, ids.length);
292                 ids = tmp;
293                 int offset = 0;
294                 for (int i = 0; i != args.length; ++i) {
295                     if (present == null || !present[i]) {
296                         ids[offset] = Integer.valueOf(i);
297                         ++offset;
298                     }
299                 }
300                 if (offset != extraCount) Kit.codeBug();
301             }
302         }
303         return ids;
304     }
305 
306     @Override
getOwnPropertyDescriptor(Context cx, Object id)307     protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) {
308       double d = ScriptRuntime.toNumber(id);
309       int index = (int) d;
310       if (d != index) {
311         return super.getOwnPropertyDescriptor(cx, id);
312       }
313       Object value = arg(index);
314       if (value == NOT_FOUND) {
315         return super.getOwnPropertyDescriptor(cx, id);
316       }
317       if (sharedWithActivation(index)) {
318         value = getFromActivation(index);
319       }
320       if (super.has(index, this)) { // the descriptor has been redefined
321         ScriptableObject desc = super.getOwnPropertyDescriptor(cx, id);
322         desc.put("value", desc, value);
323         return desc;
324       } else {
325         Scriptable scope = getParentScope();
326         if (scope == null) scope = this;
327         return buildDataDescriptor(scope, value, EMPTY);
328       }
329     }
330 
331     @Override
defineOwnProperty(Context cx, Object id, ScriptableObject desc, boolean checkValid)332     protected void defineOwnProperty(Context cx, Object id,
333                                      ScriptableObject desc,
334                                      boolean checkValid) {
335       super.defineOwnProperty(cx, id, desc, checkValid);
336 
337       double d = ScriptRuntime.toNumber(id);
338       int index = (int) d;
339       if (d != index) return;
340 
341       Object value = arg(index);
342       if (value == NOT_FOUND) return;
343 
344       if (isAccessorDescriptor(desc)) {
345         removeArg(index);
346         return;
347       }
348 
349       Object newValue = getProperty(desc, "value");
350       if (newValue == NOT_FOUND) return;
351 
352       replaceArg(index, newValue);
353 
354       if (isFalse(getProperty(desc, "writable"))) {
355         removeArg(index);
356       }
357     }
358 
359 // Fields to hold caller, callee and length properties,
360 // where NOT_FOUND value tags deleted properties.
361 // In addition if callerObj == NULL_VALUE, it tags null for scripts, as
362 // initial callerObj == null means access to caller arguments available
363 // only in JS <= 1.3 scripts
364     private Object callerObj;
365     private Object calleeObj;
366     private Object lengthObj;
367     private Object constructor;
368 
369     private NativeCall activation;
370 
371 // Initially args holds activation.getOriginalArgs(), but any modification
372 // of its elements triggers creation of a copy. If its element holds NOT_FOUND,
373 // it indicates deleted index, in which case super class is queried.
374     private Object[] args;
375 }
376