/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript; /** * This class implements the "arguments" object. * * See ECMA 10.1.8 * * @see org.mozilla.javascript.NativeCall * @author Norris Boyd */ final class Arguments extends IdScriptableObject { static final long serialVersionUID = 4275508002492040609L; private static final String FTAG = "Arguments"; public Arguments(NativeCall activation) { this.activation = activation; Scriptable parent = activation.getParentScope(); setParentScope(parent); setPrototype(ScriptableObject.getObjectPrototype(parent)); args = activation.originalArgs; lengthObj = Integer.valueOf(args.length); NativeFunction f = activation.function; calleeObj = f; int version = f.getLanguageVersion(); if (version <= Context.VERSION_1_3 && version != Context.VERSION_DEFAULT) { callerObj = null; } else { callerObj = NOT_FOUND; } } @Override public String getClassName() { return FTAG; } private Object arg(int index) { if (index < 0 || args.length <= index) return NOT_FOUND; return args[index]; } // the following helper methods assume that 0 < index < args.length private void putIntoActivation(int index, Object value) { String argName = activation.function.getParamOrVarName(index); activation.put(argName, activation, value); } private Object getFromActivation(int index) { String argName = activation.function.getParamOrVarName(index); return activation.get(argName, activation); } private void replaceArg(int index, Object value) { if (sharedWithActivation(index)) { putIntoActivation(index, value); } synchronized (this) { if (args == activation.originalArgs) { args = args.clone(); } args[index] = value; } } private void removeArg(int index) { synchronized (this) { if (args[index] != NOT_FOUND) { if (args == activation.originalArgs) { args = args.clone(); } args[index] = NOT_FOUND; } } } // end helpers @Override public boolean has(int index, Scriptable start) { if (arg(index) != NOT_FOUND) { return true; } return super.has(index, start); } @Override public Object get(int index, Scriptable start) { final Object value = arg(index); if (value == NOT_FOUND) { return super.get(index, start); } else { if (sharedWithActivation(index)) { return getFromActivation(index); } else { return value; } } } private boolean sharedWithActivation(int index) { NativeFunction f = activation.function; int definedCount = f.getParamCount(); if (index < definedCount) { // Check if argument is not hidden by later argument with the same // name as hidden arguments are not shared with activation if (index < definedCount - 1) { String argName = f.getParamOrVarName(index); for (int i = index + 1; i < definedCount; i++) { if (argName.equals(f.getParamOrVarName(i))) { return false; } } } return true; } return false; } @Override public void put(int index, Scriptable start, Object value) { if (arg(index) == NOT_FOUND) { super.put(index, start, value); } else { replaceArg(index, value); } } @Override public void delete(int index) { if (0 <= index && index < args.length) { removeArg(index); } super.delete(index); } // #string_id_map# private static final int Id_callee = 1, Id_length = 2, Id_caller = 3, MAX_INSTANCE_ID = Id_caller; @Override protected int getMaxInstanceId() { return MAX_INSTANCE_ID; } @Override protected int findInstanceIdInfo(String s) { int id; // #generated# Last update: 2010-01-06 05:48:21 ARST L0: { id = 0; String X = null; int c; int s_length = s.length(); if (s_length==6) { c=s.charAt(5); if (c=='e') { X="callee";id=Id_callee; } else if (c=='h') { X="length";id=Id_length; } else if (c=='r') { X="caller";id=Id_caller; } } if (X!=null && X!=s && !X.equals(s)) id = 0; break L0; } // #/generated# if (id == 0) return super.findInstanceIdInfo(s); int attr; switch (id) { case Id_callee: attr = calleeAttr; break; case Id_caller: attr = callerAttr; break; case Id_length: attr = lengthAttr; break; default: throw new IllegalStateException(); } return instanceIdInfo(attr, id); } // #/string_id_map# @Override protected String getInstanceIdName(int id) { switch (id) { case Id_callee: return "callee"; case Id_length: return "length"; case Id_caller: return "caller"; } return null; } @Override protected Object getInstanceIdValue(int id) { switch (id) { case Id_callee: return calleeObj; case Id_length: return lengthObj; case Id_caller: { Object value = callerObj; if (value == UniqueTag.NULL_VALUE) { value = null; } else if (value == null) { NativeCall caller = activation.parentActivationCall; if (caller != null) { value = caller.get("arguments", caller); } } return value; } } return super.getInstanceIdValue(id); } @Override protected void setInstanceIdValue(int id, Object value) { switch (id) { case Id_callee: calleeObj = value; return; case Id_length: lengthObj = value; return; case Id_caller: callerObj = (value != null) ? value : UniqueTag.NULL_VALUE; return; } super.setInstanceIdValue(id, value); } @Override protected void setInstanceIdAttributes(int id, int attr) { switch (id) { case Id_callee: calleeAttr = attr; return; case Id_length: lengthAttr = attr; return; case Id_caller: callerAttr = attr; return; } super.setInstanceIdAttributes(id, attr); } @Override Object[] getIds(boolean getAll) { Object[] ids = super.getIds(getAll); if (args.length != 0) { boolean[] present = new boolean[args.length]; int extraCount = args.length; for (int i = 0; i != ids.length; ++i) { Object id = ids[i]; if (id instanceof Integer) { int index = ((Integer)id).intValue(); if (0 <= index && index < args.length) { if (!present[index]) { present[index] = true; extraCount--; } } } } if (!getAll) { // avoid adding args which were redefined to non-enumerable for (int i = 0; i < present.length; i++) { if (!present[i] && super.has(i, this)) { present[i] = true; extraCount--; } } } if (extraCount != 0) { Object[] tmp = new Object[extraCount + ids.length]; System.arraycopy(ids, 0, tmp, extraCount, ids.length); ids = tmp; int offset = 0; for (int i = 0; i != args.length; ++i) { if (present == null || !present[i]) { ids[offset] = Integer.valueOf(i); ++offset; } } if (offset != extraCount) Kit.codeBug(); } } return ids; } @Override protected ScriptableObject getOwnPropertyDescriptor(Context cx, Object id) { double d = ScriptRuntime.toNumber(id); int index = (int) d; if (d != index) { return super.getOwnPropertyDescriptor(cx, id); } Object value = arg(index); if (value == NOT_FOUND) { return super.getOwnPropertyDescriptor(cx, id); } if (sharedWithActivation(index)) { value = getFromActivation(index); } if (super.has(index, this)) { // the descriptor has been redefined ScriptableObject desc = super.getOwnPropertyDescriptor(cx, id); desc.put("value", desc, value); return desc; } else { Scriptable scope = getParentScope(); if (scope == null) scope = this; return buildDataDescriptor(scope, value, EMPTY); } } @Override protected void defineOwnProperty(Context cx, Object id, ScriptableObject desc, boolean checkValid) { super.defineOwnProperty(cx, id, desc, checkValid); double d = ScriptRuntime.toNumber(id); int index = (int) d; if (d != index) return; Object value = arg(index); if (value == NOT_FOUND) return; if (isAccessorDescriptor(desc)) { removeArg(index); return; } Object newValue = getProperty(desc, "value"); if (newValue == NOT_FOUND) return; replaceArg(index, newValue); if (isFalse(getProperty(desc, "writable"))) { removeArg(index); } } // Fields to hold caller, callee and length properties, // where NOT_FOUND value tags deleted properties. // In addition if callerObj == NULL_VALUE, it tags null for scripts, as // initial callerObj == null means access to caller arguments available // only in JS <= 1.3 scripts private Object callerObj; private Object calleeObj; private Object lengthObj; private int callerAttr = DONTENUM; private int calleeAttr = DONTENUM; private int lengthAttr = DONTENUM; private NativeCall activation; // Initially args holds activation.getOriginalArgs(), but any modification // of its elements triggers creation of a copy. If its element holds NOT_FOUND, // it indicates deleted index, in which case super class is queried. private Object[] args; }