/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Rhino code, released * May 6, 1999. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1997-2000 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Patrick Beard * Norris Boyd * Igor Bukanov * Ethan Hugg * Bob Jervis * Roger Lawrence * Terry Lucas * Frank Mitchell * Milen Nankov * Hannes Wallnoefer * Andrew Wason * * Alternatively, the contents of this file may be used under the terms of * the GNU General Public License Version 2 or later (the "GPL"), in which * case the provisions of the GPL are applicable instead of those above. If * you wish to allow use of your version of this file only under the terms of * the GPL and not to allow others to use your version of this file under the * MPL, indicate your decision by deleting the provisions above and replacing * them with the notice and other provisions required by the GPL. If you do * not delete the provisions above, a recipient may use your version of this * file under either the MPL or the GPL. * * ***** END LICENSE BLOCK ***** */ package org.mozilla.javascript; import java.lang.reflect.*; import java.text.MessageFormat; import java.util.Locale; import java.util.ResourceBundle; import org.mozilla.javascript.xml.XMLObject; import org.mozilla.javascript.xml.XMLLib; import org.mozilla.javascript.continuations.Continuation; /** * This is the class that implements the runtime. * * @author Norris Boyd */ public class ScriptRuntime { /** * No instances should be created. */ protected ScriptRuntime() { } private static class NoSuchMethodShim implements Callable { String methodName; Callable noSuchMethodMethod; NoSuchMethodShim(Callable noSuchMethodMethod, String methodName) { this.noSuchMethodMethod = noSuchMethodMethod; this.methodName = methodName; } /** * Perform the call. * * @param cx the current Context for this thread * @param scope the scope to use to resolve properties. * @param thisObj the JavaScript <code>this</code> object * @param args the array of arguments * @return the result of the call */ public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { Object[] nestedArgs = new Object[2]; nestedArgs[0] = methodName; nestedArgs[1] = newArrayLiteral(args, null, cx, scope); return noSuchMethodMethod.call(cx, scope, thisObj, nestedArgs); } } /* * There's such a huge space (and some time) waste for the Foo.class * syntax: the compiler sticks in a test of a static field in the * enclosing class for null and the code for creating the class value. * It has to do this since the reference has to get pushed off til * executiontime (i.e. can't force an early load), but for the * 'standard' classes - especially those in java.lang, we can trust * that they won't cause problems by being loaded early. */ public final static Class BooleanClass = Kit.classOrNull("java.lang.Boolean"), ByteClass = Kit.classOrNull("java.lang.Byte"), CharacterClass = Kit.classOrNull("java.lang.Character"), ClassClass = Kit.classOrNull("java.lang.Class"), DoubleClass = Kit.classOrNull("java.lang.Double"), FloatClass = Kit.classOrNull("java.lang.Float"), IntegerClass = Kit.classOrNull("java.lang.Integer"), LongClass = Kit.classOrNull("java.lang.Long"), NumberClass = Kit.classOrNull("java.lang.Number"), ObjectClass = Kit.classOrNull("java.lang.Object"), ShortClass = Kit.classOrNull("java.lang.Short"), StringClass = Kit.classOrNull("java.lang.String"), DateClass = Kit.classOrNull("java.util.Date"); public final static Class ContextClass = Kit.classOrNull("org.mozilla.javascript.Context"), ContextFactoryClass = Kit.classOrNull("org.mozilla.javascript.ContextFactory"), FunctionClass = Kit.classOrNull("org.mozilla.javascript.Function"), ScriptableClass = Kit.classOrNull("org.mozilla.javascript.Scriptable"), ScriptableObjectClass = Kit.classOrNull("org.mozilla.javascript.ScriptableObject"); private static final String[] lazilyNames = { "RegExp", "org.mozilla.javascript.regexp.NativeRegExp", "Packages", "org.mozilla.javascript.NativeJavaTopPackage", "java", "org.mozilla.javascript.NativeJavaTopPackage", "getClass", "org.mozilla.javascript.NativeJavaTopPackage", "JavaAdapter", "org.mozilla.javascript.JavaAdapter", "JavaImporter", "org.mozilla.javascript.ImporterTopLevel", // TODO Grotesque hack using literal string (xml) just to minimize // changes for now "XML", "(xml)", "XMLList", "(xml)", "Namespace", "(xml)", "QName", "(xml)", }; private static final Object LIBRARY_SCOPE_KEY = new Object(); public static boolean isRhinoRuntimeType(Class cl) { if (cl.isPrimitive()) { return (cl != Character.TYPE); } else { return (cl == StringClass || cl == BooleanClass || NumberClass.isAssignableFrom(cl) || ScriptableClass.isAssignableFrom(cl)); } } public static ScriptableObject initStandardObjects(Context cx, ScriptableObject scope, boolean sealed) { if (scope == null) { scope = new NativeObject(); } scope.associateValue(LIBRARY_SCOPE_KEY, scope); (new ClassCache()).associate(scope); BaseFunction.init(scope, sealed); NativeObject.init(scope, sealed); Scriptable objectProto = ScriptableObject.getObjectPrototype(scope); // Function.prototype.__proto__ should be Object.prototype Scriptable functionProto = ScriptableObject.getFunctionPrototype(scope); functionProto.setPrototype(objectProto); // Set the prototype of the object passed in if need be if (scope.getPrototype() == null) scope.setPrototype(objectProto); // must precede NativeGlobal since it's needed therein NativeError.init(scope, sealed); NativeGlobal.init(cx, scope, sealed); NativeArray.init(scope, sealed); NativeString.init(scope, sealed); NativeBoolean.init(scope, sealed); NativeNumber.init(scope, sealed); NativeDate.init(scope, sealed); NativeMath.init(scope, sealed); NativeWith.init(scope, sealed); NativeCall.init(scope, sealed); NativeScript.init(scope, sealed); boolean withXml = cx.hasFeature(Context.FEATURE_E4X) && cx.getE4xImplementationFactory() != null; for (int i = 0; i != lazilyNames.length; i += 2) { String topProperty = lazilyNames[i]; String className = lazilyNames[i + 1]; if (!withXml && className.equals("(xml)")) { continue; } else if (withXml && className.equals("(xml)")) { className = cx.getE4xImplementationFactory().getImplementationClassName(); } new LazilyLoadedCtor(scope, topProperty, className, sealed); } Continuation.init(scope, sealed); return scope; } public static ScriptableObject getLibraryScopeOrNull(Scriptable scope) { ScriptableObject libScope; libScope = (ScriptableObject)ScriptableObject. getTopScopeValue(scope, LIBRARY_SCOPE_KEY); return libScope; } // It is public so NativeRegExp can access it . public static boolean isJSLineTerminator(int c) { // Optimization for faster check for eol character: // they do not have 0xDFD0 bits set if ((c & 0xDFD0) != 0) { return false; } return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029; } public static Boolean wrapBoolean(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; } public static Integer wrapInt(int i) { return new Integer(i); } public static Number wrapNumber(double x) { if (x != x) { return ScriptRuntime.NaNobj; } return new Double(x); } /** * Convert the value to a boolean. * * See ECMA 9.2. */ public static boolean toBoolean(Object val) { for (;;) { if (val instanceof Boolean) return ((Boolean) val).booleanValue(); if (val == null || val == Undefined.instance) return false; if (val instanceof String) return ((String) val).length() != 0; if (val instanceof Number) { double d = ((Number) val).doubleValue(); return (d == d && d != 0.0); } if (val instanceof Scriptable) { if (Context.getContext().isVersionECMA1()) { // pure ECMA return true; } // ECMA extension val = ((Scriptable) val).getDefaultValue(BooleanClass); if (val instanceof Scriptable) throw errorWithClassName("msg.primitive.expected", val); continue; } warnAboutNonJSObject(val); return true; } } public static boolean toBoolean(Object[] args, int index) { return (index < args.length) ? toBoolean(args[index]) : false; } /** * Convert the value to a number. * * See ECMA 9.3. */ public static double toNumber(Object val) { for (;;) { if (val instanceof Number) return ((Number) val).doubleValue(); if (val == null) return +0.0; if (val == Undefined.instance) return NaN; if (val instanceof String) return toNumber((String) val); if (val instanceof Boolean) return ((Boolean) val).booleanValue() ? 1 : +0.0; if (val instanceof Scriptable) { val = ((Scriptable) val).getDefaultValue(NumberClass); if (val instanceof Scriptable) throw errorWithClassName("msg.primitive.expected", val); continue; } warnAboutNonJSObject(val); return NaN; } } public static double toNumber(Object[] args, int index) { return (index < args.length) ? toNumber(args[index]) : NaN; } // Can not use Double.NaN defined as 0.0d / 0.0 as under the Microsoft VM, // versions 2.01 and 3.0P1, that causes some uses (returns at least) of // Double.NaN to be converted to 1.0. // So we use ScriptRuntime.NaN instead of Double.NaN. public static final double NaN = Double.longBitsToDouble(0x7ff8000000000000L); // A similar problem exists for negative zero. public static final double negativeZero = Double.longBitsToDouble(0x8000000000000000L); public static final Double NaNobj = new Double(NaN); /* * Helper function for toNumber, parseInt, and TokenStream.getToken. */ static double stringToNumber(String s, int start, int radix) { char digitMax = '9'; char lowerCaseBound = 'a'; char upperCaseBound = 'A'; int len = s.length(); if (radix < 10) { digitMax = (char) ('0' + radix - 1); } if (radix > 10) { lowerCaseBound = (char) ('a' + radix - 10); upperCaseBound = (char) ('A' + radix - 10); } int end; double sum = 0.0; for (end=start; end < len; end++) { char c = s.charAt(end); int newDigit; if ('0' <= c && c <= digitMax) newDigit = c - '0'; else if ('a' <= c && c < lowerCaseBound) newDigit = c - 'a' + 10; else if ('A' <= c && c < upperCaseBound) newDigit = c - 'A' + 10; else break; sum = sum*radix + newDigit; } if (start == end) { return NaN; } if (sum >= 9007199254740992.0) { if (radix == 10) { /* If we're accumulating a decimal number and the number * is >= 2^53, then the result from the repeated multiply-add * above may be inaccurate. Call Java to get the correct * answer. */ try { return Double.valueOf(s.substring(start, end)).doubleValue(); } catch (NumberFormatException nfe) { return NaN; } } else if (radix == 2 || radix == 4 || radix == 8 || radix == 16 || radix == 32) { /* The number may also be inaccurate for one of these bases. * This happens if the addition in value*radix + digit causes * a round-down to an even least significant mantissa bit * when the first dropped bit is a one. If any of the * following digits in the number (which haven't been added * in yet) are nonzero then the correct action would have * been to round up instead of down. An example of this * occurs when reading the number 0x1000000000000081, which * rounds to 0x1000000000000000 instead of 0x1000000000000100. */ int bitShiftInChar = 1; int digit = 0; final int SKIP_LEADING_ZEROS = 0; final int FIRST_EXACT_53_BITS = 1; final int AFTER_BIT_53 = 2; final int ZEROS_AFTER_54 = 3; final int MIXED_AFTER_54 = 4; int state = SKIP_LEADING_ZEROS; int exactBitsLimit = 53; double factor = 0.0; boolean bit53 = false; // bit54 is the 54th bit (the first dropped from the mantissa) boolean bit54 = false; for (;;) { if (bitShiftInChar == 1) { if (start == end) break; digit = s.charAt(start++); if ('0' <= digit && digit <= '9') digit -= '0'; else if ('a' <= digit && digit <= 'z') digit -= 'a' - 10; else digit -= 'A' - 10; bitShiftInChar = radix; } bitShiftInChar >>= 1; boolean bit = (digit & bitShiftInChar) != 0; switch (state) { case SKIP_LEADING_ZEROS: if (bit) { --exactBitsLimit; sum = 1.0; state = FIRST_EXACT_53_BITS; } break; case FIRST_EXACT_53_BITS: sum *= 2.0; if (bit) sum += 1.0; --exactBitsLimit; if (exactBitsLimit == 0) { bit53 = bit; state = AFTER_BIT_53; } break; case AFTER_BIT_53: bit54 = bit; factor = 2.0; state = ZEROS_AFTER_54; break; case ZEROS_AFTER_54: if (bit) { state = MIXED_AFTER_54; } // fallthrough case MIXED_AFTER_54: factor *= 2; break; } } switch (state) { case SKIP_LEADING_ZEROS: sum = 0.0; break; case FIRST_EXACT_53_BITS: case AFTER_BIT_53: // do nothing break; case ZEROS_AFTER_54: // x1.1 -> x1 + 1 (round up) // x0.1 -> x0 (round down) if (bit54 & bit53) sum += 1.0; sum *= factor; break; case MIXED_AFTER_54: // x.100...1.. -> x + 1 (round up) // x.0anything -> x (round down) if (bit54) sum += 1.0; sum *= factor; break; } } /* We don't worry about inaccurate numbers for any other base. */ } return sum; } /** * ToNumber applied to the String type * * See ECMA 9.3.1 */ public static double toNumber(String s) { int len = s.length(); int start = 0; char startChar; for (;;) { if (start == len) { // Empty or contains only whitespace return +0.0; } startChar = s.charAt(start); if (!Character.isWhitespace(startChar)) break; start++; } if (startChar == '0') { if (start + 2 < len) { int c1 = s.charAt(start + 1); if (c1 == 'x' || c1 == 'X') { // A hexadecimal number return stringToNumber(s, start + 2, 16); } } } else if (startChar == '+' || startChar == '-') { if (start + 3 < len && s.charAt(start + 1) == '0') { int c2 = s.charAt(start + 2); if (c2 == 'x' || c2 == 'X') { // A hexadecimal number with sign double val = stringToNumber(s, start + 3, 16); return startChar == '-' ? -val : val; } } } int end = len - 1; char endChar; while (Character.isWhitespace(endChar = s.charAt(end))) end--; if (endChar == 'y') { // check for "Infinity" if (startChar == '+' || startChar == '-') start++; if (start + 7 == end && s.regionMatches(start, "Infinity", 0, 8)) return startChar == '-' ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; return NaN; } // A non-hexadecimal, non-infinity number: // just try a normal floating point conversion String sub = s.substring(start, end+1); if (MSJVM_BUG_WORKAROUNDS) { // The MS JVM will accept non-conformant strings // rather than throwing a NumberFormatException // as it should. for (int i=sub.length()-1; i >= 0; i--) { char c = sub.charAt(i); if (('0' <= c && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') continue; return NaN; } } try { return Double.valueOf(sub).doubleValue(); } catch (NumberFormatException ex) { return NaN; } } /** * Helper function for builtin objects that use the varargs form. * ECMA function formal arguments are undefined if not supplied; * this function pads the argument array out to the expected * length, if necessary. */ public static Object[] padArguments(Object[] args, int count) { if (count < args.length) return args; int i; Object[] result = new Object[count]; for (i = 0; i < args.length; i++) { result[i] = args[i]; } for (; i < count; i++) { result[i] = Undefined.instance; } return result; } /* Work around Microsoft Java VM bugs. */ private final static boolean MSJVM_BUG_WORKAROUNDS = true; public static String escapeString(String s) { return escapeString(s, '"'); } /** * For escaping strings printed by object and array literals; not quite * the same as 'escape.' */ public static String escapeString(String s, char escapeQuote) { if (!(escapeQuote == '"' || escapeQuote == '\'')) Kit.codeBug(); StringBuffer sb = null; for(int i = 0, L = s.length(); i != L; ++i) { int c = s.charAt(i); if (' ' <= c && c <= '~' && c != escapeQuote && c != '\\') { // an ordinary print character (like C isprint()) and not " // or \ . if (sb != null) { sb.append((char)c); } continue; } if (sb == null) { sb = new StringBuffer(L + 3); sb.append(s); sb.setLength(i); } int escape = -1; switch (c) { case '\b': escape = 'b'; break; case '\f': escape = 'f'; break; case '\n': escape = 'n'; break; case '\r': escape = 'r'; break; case '\t': escape = 't'; break; case 0xb: escape = 'v'; break; // Java lacks \v. case ' ': escape = ' '; break; case '\\': escape = '\\'; break; } if (escape >= 0) { // an \escaped sort of character sb.append('\\'); sb.append((char)escape); } else if (c == escapeQuote) { sb.append('\\'); sb.append(escapeQuote); } else { int hexSize; if (c < 256) { // 2-digit hex sb.append("\\x"); hexSize = 2; } else { // Unicode. sb.append("\\u"); hexSize = 4; } // append hexadecimal form of c left-padded with 0 for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) { int digit = 0xf & (c >> shift); int hc = (digit < 10) ? '0' + digit : 'a' - 10 + digit; sb.append((char)hc); } } } return (sb == null) ? s : sb.toString(); } static boolean isValidIdentifierName(String s) { int L = s.length(); if (L == 0) return false; if (!Character.isJavaIdentifierStart(s.charAt(0))) return false; for (int i = 1; i != L; ++i) { if (!Character.isJavaIdentifierPart(s.charAt(i))) return false; } return !TokenStream.isKeyword(s); } /** * Convert the value to a string. * * See ECMA 9.8. */ public static String toString(Object val) { for (;;) { if (val == null) { return "null"; } if (val == Undefined.instance) { return "undefined"; } if (val instanceof String) { return (String)val; } if (val instanceof Number) { // XXX should we just teach NativeNumber.stringValue() // about Numbers? return numberToString(((Number)val).doubleValue(), 10); } if (val instanceof Scriptable) { val = ((Scriptable) val).getDefaultValue(StringClass); if (val instanceof Scriptable) { throw errorWithClassName("msg.primitive.expected", val); } continue; } return val.toString(); } } static String defaultObjectToString(Scriptable obj) { return "[object " + obj.getClassName() + ']'; } public static String toString(Object[] args, int index) { return (index < args.length) ? toString(args[index]) : "undefined"; } /** * Optimized version of toString(Object) for numbers. */ public static String toString(double val) { return numberToString(val, 10); } public static String numberToString(double d, int base) { if (d != d) return "NaN"; if (d == Double.POSITIVE_INFINITY) return "Infinity"; if (d == Double.NEGATIVE_INFINITY) return "-Infinity"; if (d == 0.0) return "0"; if ((base < 2) || (base > 36)) { throw Context.reportRuntimeError1( "msg.bad.radix", Integer.toString(base)); } if (base != 10) { return DToA.JS_dtobasestr(base, d); } else { StringBuffer result = new StringBuffer(); DToA.JS_dtostr(result, DToA.DTOSTR_STANDARD, 0, d); return result.toString(); } } static String uneval(Context cx, Scriptable scope, Object value) { if (value == null) { return "null"; } if (value == Undefined.instance) { return "undefined"; } if (value instanceof String) { String escaped = escapeString((String)value); StringBuffer sb = new StringBuffer(escaped.length() + 2); sb.append('\"'); sb.append(escaped); sb.append('\"'); return sb.toString(); } if (value instanceof Number) { double d = ((Number)value).doubleValue(); if (d == 0 && 1 / d < 0) { return "-0"; } return toString(d); } if (value instanceof Boolean) { return toString(value); } if (value instanceof Scriptable) { Scriptable obj = (Scriptable)value; Object v = ScriptableObject.getProperty(obj, "toSource"); if (v instanceof Function) { Function f = (Function)v; return toString(f.call(cx, scope, obj, emptyArgs)); } return toString(value); } warnAboutNonJSObject(value); return value.toString(); } static String defaultObjectToSource(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { boolean toplevel, iterating; if (cx.iterating == null) { toplevel = true; iterating = false; cx.iterating = new ObjToIntMap(31); } else { toplevel = false; iterating = cx.iterating.has(thisObj); } StringBuffer result = new StringBuffer(128); if (toplevel) { result.append("("); } result.append('{'); // Make sure cx.iterating is set to null when done // so we don't leak memory try { if (!iterating) { cx.iterating.intern(thisObj); // stop recursion. Object[] ids = thisObj.getIds(); for (int i=0; i < ids.length; i++) { Object id = ids[i]; Object value; if (id instanceof Integer) { int intId = ((Integer)id).intValue(); value = thisObj.get(intId, thisObj); if (value == Scriptable.NOT_FOUND) continue; // a property has been removed if (i > 0) result.append(", "); result.append(intId); } else { String strId = (String)id; value = thisObj.get(strId, thisObj); if (value == Scriptable.NOT_FOUND) continue; // a property has been removed if (i > 0) result.append(", "); if (ScriptRuntime.isValidIdentifierName(strId)) { result.append(strId); } else { result.append('\''); result.append( ScriptRuntime.escapeString(strId, '\'')); result.append('\''); } } result.append(':'); result.append(ScriptRuntime.uneval(cx, scope, value)); } } } finally { if (toplevel) { cx.iterating = null; } } result.append('}'); if (toplevel) { result.append(')'); } return result.toString(); } public static Scriptable toObject(Scriptable scope, Object val) { if (val instanceof Scriptable) { return (Scriptable)val; } return toObject(Context.getContext(), scope, val); } public static Scriptable toObjectOrNull(Context cx, Object obj) { if (obj instanceof Scriptable) { return (Scriptable)obj; } else if (obj != null && obj != Undefined.instance) { return toObject(cx, getTopCallScope(cx), obj); } return null; } /** * @deprecated Use {@link #toObject(Scriptable, Object)} instead. */ public static Scriptable toObject(Scriptable scope, Object val, Class staticClass) { if (val instanceof Scriptable) { return (Scriptable)val; } return toObject(Context.getContext(), scope, val); } /** * Convert the value to an object. * * See ECMA 9.9. */ public static Scriptable toObject(Context cx, Scriptable scope, Object val) { if (val instanceof Scriptable) { return (Scriptable) val; } if (val == null) { throw typeError0("msg.null.to.object"); } if (val == Undefined.instance) { throw typeError0("msg.undef.to.object"); } String className = val instanceof String ? "String" : val instanceof Number ? "Number" : val instanceof Boolean ? "Boolean" : null; if (className != null) { Object[] args = { val }; scope = ScriptableObject.getTopLevelScope(scope); return newObject(cx, scope, className, args); } // Extension: Wrap as a LiveConnect object. Object wrapped = cx.getWrapFactory().wrap(cx, scope, val, null); if (wrapped instanceof Scriptable) return (Scriptable) wrapped; throw errorWithClassName("msg.invalid.type", val); } /** * @deprecated Use {@link #toObject(Context, Scriptable, Object)} instead. */ public static Scriptable toObject(Context cx, Scriptable scope, Object val, Class staticClass) { return toObject(cx, scope, val); } /** * @deprecated The method is only present for compatibility. */ public static Object call(Context cx, Object fun, Object thisArg, Object[] args, Scriptable scope) { if (!(fun instanceof Function)) { throw notFunctionError(toString(fun)); } Function function = (Function)fun; Scriptable thisObj = toObjectOrNull(cx, thisArg); if (thisObj == null) { throw undefCallError(thisObj, "function"); } return function.call(cx, scope, thisObj, args); } public static Scriptable newObject(Context cx, Scriptable scope, String constructorName, Object[] args) { scope = ScriptableObject.getTopLevelScope(scope); Function ctor = getExistingCtor(cx, scope, constructorName); if (args == null) { args = ScriptRuntime.emptyArgs; } return ctor.construct(cx, scope, args); } /** * * See ECMA 9.4. */ public static double toInteger(Object val) { return toInteger(toNumber(val)); } // convenience method public static double toInteger(double d) { // if it's NaN if (d != d) return +0.0; if (d == 0.0 || d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) return d; if (d > 0.0) return Math.floor(d); else return Math.ceil(d); } public static double toInteger(Object[] args, int index) { return (index < args.length) ? toInteger(args[index]) : +0.0; } /** * * See ECMA 9.5. */ public static int toInt32(Object val) { // short circuit for common integer values if (val instanceof Integer) return ((Integer)val).intValue(); return toInt32(toNumber(val)); } public static int toInt32(Object[] args, int index) { return (index < args.length) ? toInt32(args[index]) : 0; } public static int toInt32(double d) { int id = (int)d; if (id == d) { // This covers -0.0 as well return id; } if (d != d || d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) { return 0; } d = (d >= 0) ? Math.floor(d) : Math.ceil(d); double two32 = 4294967296.0; d = Math.IEEEremainder(d, two32); // (double)(long)d == d should hold here long l = (long)d; // returning (int)d does not work as d can be outside int range // but the result must always be 32 lower bits of l return (int)l; } /** * See ECMA 9.6. * @return long value representing 32 bits unsigned integer */ public static long toUint32(double d) { long l = (long)d; if (l == d) { // This covers -0.0 as well return l & 0xffffffffL; } if (d != d || d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) { return 0; } d = (d >= 0) ? Math.floor(d) : Math.ceil(d); // 0x100000000 gives me a numeric overflow... double two32 = 4294967296.0; l = (long)Math.IEEEremainder(d, two32); return l & 0xffffffffL; } public static long toUint32(Object val) { return toUint32(toNumber(val)); } /** * * See ECMA 9.7. */ public static char toUint16(Object val) { double d = toNumber(val); int i = (int)d; if (i == d) { return (char)i; } if (d != d || d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) { return 0; } d = (d >= 0) ? Math.floor(d) : Math.ceil(d); int int16 = 0x10000; i = (int)Math.IEEEremainder(d, int16); return (char)i; } // XXX: this is until setDefaultNamespace will learn how to store NS // properly and separates namespace form Scriptable.get etc. private static final String DEFAULT_NS_TAG = "__default_namespace__"; public static Object setDefaultNamespace(Object namespace, Context cx) { Scriptable scope = cx.currentActivationCall; if (scope == null) { scope = getTopCallScope(cx); } XMLLib xmlLib = currentXMLLib(cx); Object ns = xmlLib.toDefaultXmlNamespace(cx, namespace); // XXX : this should be in separated namesapce from Scriptable.get/put if (!scope.has(DEFAULT_NS_TAG, scope)) { // XXX: this is racy of cause ScriptableObject.defineProperty(scope, DEFAULT_NS_TAG, ns, ScriptableObject.PERMANENT | ScriptableObject.DONTENUM); } else { scope.put(DEFAULT_NS_TAG, scope, ns); } return Undefined.instance; } public static Object searchDefaultNamespace(Context cx) { Scriptable scope = cx.currentActivationCall; if (scope == null) { scope = getTopCallScope(cx); } Object nsObject; for (;;) { Scriptable parent = scope.getParentScope(); if (parent == null) { nsObject = ScriptableObject.getProperty(scope, DEFAULT_NS_TAG); if (nsObject == Scriptable.NOT_FOUND) { return null; } break; } nsObject = scope.get(DEFAULT_NS_TAG, scope); if (nsObject != Scriptable.NOT_FOUND) { break; } scope = parent; } return nsObject; } public static Object getTopLevelProp(Scriptable scope, String id) { scope = ScriptableObject.getTopLevelScope(scope); return ScriptableObject.getProperty(scope, id); } static Function getExistingCtor(Context cx, Scriptable scope, String constructorName) { Object ctorVal = ScriptableObject.getProperty(scope, constructorName); if (ctorVal instanceof Function) { return (Function)ctorVal; } if (ctorVal == Scriptable.NOT_FOUND) { throw Context.reportRuntimeError1( "msg.ctor.not.found", constructorName); } else { throw Context.reportRuntimeError1( "msg.not.ctor", constructorName); } } /** * Return -1L if str is not an index or the index value as lower 32 * bits of the result. */ private static long indexFromString(String str) { // The length of the decimal string representation of // Integer.MAX_VALUE, 2147483647 final int MAX_VALUE_LENGTH = 10; int len = str.length(); if (len > 0) { int i = 0; boolean negate = false; int c = str.charAt(0); if (c == '-') { if (len > 1) { c = str.charAt(1); i = 1; negate = true; } } c -= '0'; if (0 <= c && c <= 9 && len <= (negate ? MAX_VALUE_LENGTH + 1 : MAX_VALUE_LENGTH)) { // Use negative numbers to accumulate index to handle // Integer.MIN_VALUE that is greater by 1 in absolute value // then Integer.MAX_VALUE int index = -c; int oldIndex = 0; i++; if (index != 0) { // Note that 00, 01, 000 etc. are not indexes while (i != len && 0 <= (c = str.charAt(i) - '0') && c <= 9) { oldIndex = index; index = 10 * index - c; i++; } } // Make sure all characters were consumed and that it couldn't // have overflowed. if (i == len && (oldIndex > (Integer.MIN_VALUE / 10) || (oldIndex == (Integer.MIN_VALUE / 10) && c <= (negate ? -(Integer.MIN_VALUE % 10) : (Integer.MAX_VALUE % 10))))) { return 0xFFFFFFFFL & (negate ? index : -index); } } } return -1L; } /** * If str is a decimal presentation of Uint32 value, return it as long. * Othewise return -1L; */ public static long testUint32String(String str) { // The length of the decimal string representation of // UINT32_MAX_VALUE, 4294967296 final int MAX_VALUE_LENGTH = 10; int len = str.length(); if (1 <= len && len <= MAX_VALUE_LENGTH) { int c = str.charAt(0); c -= '0'; if (c == 0) { // Note that 00,01 etc. are not valid Uint32 presentations return (len == 1) ? 0L : -1L; } if (1 <= c && c <= 9) { long v = c; for (int i = 1; i != len; ++i) { c = str.charAt(i) - '0'; if (!(0 <= c && c <= 9)) { return -1; } v = 10 * v + c; } // Check for overflow if ((v >>> 32) == 0) { return v; } } } return -1; } /** * If s represents index, then return index value wrapped as Integer * and othewise return s. */ static Object getIndexObject(String s) { long indexTest = indexFromString(s); if (indexTest >= 0) { return new Integer((int)indexTest); } return s; } /** * If d is exact int value, return its value wrapped as Integer * and othewise return d converted to String. */ static Object getIndexObject(double d) { int i = (int)d; if (i == d) { return new Integer(i); } return toString(d); } /** * If toString(id) is a decimal presentation of int32 value, then id * is index. In this case return null and make the index available * as ScriptRuntime.lastIndexResult(cx). Otherwise return toString(id). */ static String toStringIdOrIndex(Context cx, Object id) { if (id instanceof Number) { double d = ((Number)id).doubleValue(); int index = (int)d; if (index == d) { storeIndexResult(cx, index); return null; } return toString(id); } else { String s; if (id instanceof String) { s = (String)id; } else { s = toString(id); } long indexTest = indexFromString(s); if (indexTest >= 0) { storeIndexResult(cx, (int)indexTest); return null; } return s; } } /** * Call obj.[[Get]](id) */ public static Object getObjectElem(Object obj, Object elem, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefReadError(obj, elem); } return getObjectElem(sobj, elem, cx); } public static Object getObjectElem(Scriptable obj, Object elem, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; return xmlObject.ecmaGet(cx, elem); } Object result; String s = toStringIdOrIndex(cx, elem); if (s == null) { int index = lastIndexResult(cx); result = ScriptableObject.getProperty(obj, index); } else { result = ScriptableObject.getProperty(obj, s); } if (result == Scriptable.NOT_FOUND) { result = Undefined.instance; } return result; } /** * Version of getObjectElem when elem is a valid JS identifier name. */ public static Object getObjectProp(Object obj, String property, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefReadError(obj, property); } return getObjectProp(sobj, property, cx); } public static Object getObjectProp(Scriptable obj, String property, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; return xmlObject.ecmaGet(cx, property); } Object result = ScriptableObject.getProperty(obj, property); if (result == Scriptable.NOT_FOUND) { if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) { Context.reportWarning(ScriptRuntime.getMessage1( "msg.ref.undefined.prop", property)); } result = Undefined.instance; } return result; } /* * A cheaper and less general version of the above for well-known argument * types. */ public static Object getObjectIndex(Object obj, double dblIndex, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefReadError(obj, toString(dblIndex)); } int index = (int)dblIndex; if (index == dblIndex) { return getObjectIndex(sobj, index, cx); } else { String s = toString(dblIndex); return getObjectProp(sobj, s, cx); } } public static Object getObjectIndex(Scriptable obj, int index, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; return xmlObject.ecmaGet(cx, new Integer(index)); } Object result = ScriptableObject.getProperty(obj, index); if (result == Scriptable.NOT_FOUND) { result = Undefined.instance; } return result; } /* * Call obj.[[Put]](id, value) */ public static Object setObjectElem(Object obj, Object elem, Object value, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefWriteError(obj, elem, value); } return setObjectElem(sobj, elem, value, cx); } public static Object setObjectElem(Scriptable obj, Object elem, Object value, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; xmlObject.ecmaPut(cx, elem, value); return value; } String s = toStringIdOrIndex(cx, elem); if (s == null) { int index = lastIndexResult(cx); ScriptableObject.putProperty(obj, index, value); } else { ScriptableObject.putProperty(obj, s, value); } return value; } /** * Version of setObjectElem when elem is a valid JS identifier name. */ public static Object setObjectProp(Object obj, String property, Object value, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefWriteError(obj, property, value); } return setObjectProp(sobj, property, value, cx); } public static Object setObjectProp(Scriptable obj, String property, Object value, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; xmlObject.ecmaPut(cx, property, value); } else { ScriptableObject.putProperty(obj, property, value); } return value; } /* * A cheaper and less general version of the above for well-known argument * types. */ public static Object setObjectIndex(Object obj, double dblIndex, Object value, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw undefWriteError(obj, String.valueOf(dblIndex), value); } int index = (int)dblIndex; if (index == dblIndex) { return setObjectIndex(sobj, index, value, cx); } else { String s = toString(dblIndex); return setObjectProp(sobj, s, value, cx); } } public static Object setObjectIndex(Scriptable obj, int index, Object value, Context cx) { if (obj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)obj; xmlObject.ecmaPut(cx, new Integer(index), value); } else { ScriptableObject.putProperty(obj, index, value); } return value; } public static boolean deleteObjectElem(Scriptable target, Object elem, Context cx) { boolean result; if (target instanceof XMLObject) { XMLObject xmlObject = (XMLObject)target; result = xmlObject.ecmaDelete(cx, elem); } else { String s = toStringIdOrIndex(cx, elem); if (s == null) { int index = lastIndexResult(cx); result = ScriptableObject.deleteProperty(target, index); } else { result = ScriptableObject.deleteProperty(target, s); } } return result; } public static boolean hasObjectElem(Scriptable target, Object elem, Context cx) { boolean result; if (target instanceof XMLObject) { XMLObject xmlObject = (XMLObject)target; result = xmlObject.ecmaHas(cx, elem); } else { String s = toStringIdOrIndex(cx, elem); if (s == null) { int index = lastIndexResult(cx); result = ScriptableObject.hasProperty(target, index); } else { result = ScriptableObject.hasProperty(target, s); } } return result; } public static Object refGet(Ref ref, Context cx) { return ref.get(cx); } public static Object refSet(Ref ref, Object value, Context cx) { return ref.set(cx, value); } public static Object refDel(Ref ref, Context cx) { return wrapBoolean(ref.delete(cx)); } static boolean isSpecialProperty(String s) { return s.equals("__proto__") || s.equals("__parent__"); } public static Ref specialRef(Object obj, String specialProperty, Context cx) { return SpecialRef.createSpecial(cx, obj, specialProperty); } /** * The delete operator * * See ECMA 11.4.1 * * In ECMA 0.19, the description of the delete operator (11.4.1) * assumes that the [[Delete]] method returns a value. However, * the definition of the [[Delete]] operator (8.6.2.5) does not * define a return value. Here we assume that the [[Delete]] * method doesn't return a value. */ public static Object delete(Object obj, Object id, Context cx) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { String idStr = (id == null) ? "null" : id.toString(); throw typeError2("msg.undef.prop.delete", toString(obj), idStr); } boolean result = deleteObjectElem(sobj, id, cx); return wrapBoolean(result); } /** * Looks up a name in the scope chain and returns its value. */ public static Object name(Context cx, Scriptable scope, String name) { Scriptable parent = scope.getParentScope(); if (parent == null) { Object result = topScopeName(cx, scope, name); if (result == Scriptable.NOT_FOUND) { throw notFoundError(scope, name); } return result; } return nameOrFunction(cx, scope, parent, name, false); } private static Object nameOrFunction(Context cx, Scriptable scope, Scriptable parentScope, String name, boolean asFunctionCall) { Object result; Scriptable thisObj = scope; // It is used only if asFunctionCall==true. XMLObject firstXMLObject = null; for (;;) { if (scope instanceof NativeWith) { Scriptable withObj = scope.getPrototype(); if (withObj instanceof XMLObject) { XMLObject xmlObj = (XMLObject)withObj; if (xmlObj.ecmaHas(cx, name)) { // function this should be the target object of with thisObj = xmlObj; result = xmlObj.ecmaGet(cx, name); break; } if (firstXMLObject == null) { firstXMLObject = xmlObj; } } else { result = ScriptableObject.getProperty(withObj, name); if (result != Scriptable.NOT_FOUND) { // function this should be the target object of with thisObj = withObj; break; } } } else if (scope instanceof NativeCall) { // NativeCall does not prototype chain and Scriptable.get // can be called directly. result = scope.get(name, scope); if (result != Scriptable.NOT_FOUND) { if (asFunctionCall) { // ECMA 262 requires that this for nested funtions // should be top scope thisObj = ScriptableObject. getTopLevelScope(parentScope); } break; } } else { // Can happen if Rhino embedding decided that nested // scopes are useful for what ever reasons. result = ScriptableObject.getProperty(scope, name); if (result != Scriptable.NOT_FOUND) { thisObj = scope; break; } } scope = parentScope; parentScope = parentScope.getParentScope(); if (parentScope == null) { result = topScopeName(cx, scope, name); if (result == Scriptable.NOT_FOUND) { if (firstXMLObject == null || asFunctionCall) { throw notFoundError(scope, name); } // The name was not found, but we did find an XML // object in the scope chain and we are looking for name, // not function. The result should be an empty XMLList // in name context. result = firstXMLObject.ecmaGet(cx, name); } // For top scope thisObj for functions is always scope itself. thisObj = scope; break; } } if (asFunctionCall) { if (!(result instanceof Callable)) { throw notFunctionError(result, name); } storeScriptable(cx, thisObj); } return result; } private static Object topScopeName(Context cx, Scriptable scope, String name) { if (cx.useDynamicScope) { scope = checkDynamicScope(cx.topCallScope, scope); } return ScriptableObject.getProperty(scope, name); } /** * Returns the object in the scope chain that has a given property. * * The order of evaluation of an assignment expression involves * evaluating the lhs to a reference, evaluating the rhs, and then * modifying the reference with the rhs value. This method is used * to 'bind' the given name to an object containing that property * so that the side effects of evaluating the rhs do not affect * which property is modified. * Typically used in conjunction with setName. * * See ECMA 10.1.4 */ public static Scriptable bind(Context cx, Scriptable scope, String id) { Scriptable firstXMLObject = null; Scriptable parent = scope.getParentScope(); childScopesChecks: if (parent != null) { // Check for possibly nested "with" scopes first while (scope instanceof NativeWith) { Scriptable withObj = scope.getPrototype(); if (withObj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)withObj; if (xmlObject.ecmaHas(cx, id)) { return xmlObject; } if (firstXMLObject == null) { firstXMLObject = xmlObject; } } else { if (ScriptableObject.hasProperty(withObj, id)) { return withObj; } } scope = parent; parent = parent.getParentScope(); if (parent == null) { break childScopesChecks; } } for (;;) { if (ScriptableObject.hasProperty(scope, id)) { return scope; } scope = parent; parent = parent.getParentScope(); if (parent == null) { break childScopesChecks; } } } // scope here is top scope if (cx.useDynamicScope) { scope = checkDynamicScope(cx.topCallScope, scope); } if (ScriptableObject.hasProperty(scope, id)) { return scope; } // Nothing was found, but since XML objects always bind // return one if found return firstXMLObject; } public static Object setName(Scriptable bound, Object value, Context cx, Scriptable scope, String id) { if (bound != null) { if (bound instanceof XMLObject) { XMLObject xmlObject = (XMLObject)bound; xmlObject.ecmaPut(cx, id, value); } else { ScriptableObject.putProperty(bound, id, value); } } else { // "newname = 7;", where 'newname' has not yet // been defined, creates a new property in the // top scope unless strict mode is specified. if (cx.hasFeature(Context.FEATURE_STRICT_MODE) || cx.hasFeature(Context.FEATURE_STRICT_VARS)) { Context.reportWarning( ScriptRuntime.getMessage1("msg.assn.create.strict", id)); } // Find the top scope by walking up the scope chain. bound = ScriptableObject.getTopLevelScope(scope); if (cx.useDynamicScope) { bound = checkDynamicScope(cx.topCallScope, bound); } bound.put(id, bound, value); } return value; } public static Object setConst(Scriptable bound, Object value, Context cx, String id) { if (bound instanceof XMLObject) { XMLObject xmlObject = (XMLObject)bound; xmlObject.ecmaPut(cx, id, value); } else { ScriptableObject.putConstProperty(bound, id, value); } return value; } /** * This is the enumeration needed by the for..in statement. * * See ECMA 12.6.3. * * IdEnumeration maintains a ObjToIntMap to make sure a given * id is enumerated only once across multiple objects in a * prototype chain. * * XXX - ECMA delete doesn't hide properties in the prototype, * but js/ref does. This means that the js/ref for..in can * avoid maintaining a hash table and instead perform lookups * to see if a given property has already been enumerated. * */ private static class IdEnumeration { Scriptable obj; Object[] ids; int index; ObjToIntMap used; String currentId; boolean enumValues; } public static Object enumInit(Object value, Context cx, boolean enumValues) { IdEnumeration x = new IdEnumeration(); x.obj = toObjectOrNull(cx, value); if (x.obj != null) { // null or undefined do not cause errors but rather lead to empty // "for in" loop x.enumValues = enumValues; // enumInit should read all initial ids before returning // or "for (a.i in a)" would wrongly enumerate i in a as well enumChangeObject(x); } return x; } public static Boolean enumNext(Object enumObj) { // OPT this could be more efficient boolean result; IdEnumeration x = (IdEnumeration)enumObj; for (;;) { if (x.obj == null) { result = false; break; } if (x.index == x.ids.length) { x.obj = x.obj.getPrototype(); enumChangeObject(x); continue; } Object id = x.ids[x.index++]; if (x.used != null && x.used.has(id)) { continue; } if (id instanceof String) { String strId = (String)id; if (!x.obj.has(strId, x.obj)) continue; // must have been deleted x.currentId = strId; } else { int intId = ((Number)id).intValue(); if (!x.obj.has(intId, x.obj)) continue; // must have been deleted x.currentId = String.valueOf(intId); } result = true; break; } return wrapBoolean(result); } public static Object enumId(Object enumObj, Context cx) { IdEnumeration x = (IdEnumeration)enumObj; if (!x.enumValues) return x.currentId; Object result; String s = toStringIdOrIndex(cx, x.currentId); if (s == null) { int index = lastIndexResult(cx); result = x.obj.get(index, x.obj); } else { result = x.obj.get(s, x.obj); } return result; } private static void enumChangeObject(IdEnumeration x) { Object[] ids = null; while (x.obj != null) { ids = x.obj.getIds(); if (ids.length != 0) { break; } x.obj = x.obj.getPrototype(); } if (x.obj != null && x.ids != null) { Object[] previous = x.ids; int L = previous.length; if (x.used == null) { x.used = new ObjToIntMap(L); } for (int i = 0; i != L; ++i) { x.used.intern(previous[i]); } } x.ids = ids; x.index = 0; } /** * Prepare for calling name(...): return function corresponding to * name and make current top scope available * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj. * The caller must call ScriptRuntime.lastStoredScriptable() immediately * after calling this method. */ public static Callable getNameFunctionAndThis(String name, Context cx, Scriptable scope) { Scriptable parent = scope.getParentScope(); if (parent == null) { Object result = topScopeName(cx, scope, name); if (!(result instanceof Callable)) { if (result == Scriptable.NOT_FOUND) { throw notFoundError(scope, name); } else { throw notFunctionError(result, name); } } // Top scope is not NativeWith or NativeCall => thisObj == scope Scriptable thisObj = scope; storeScriptable(cx, thisObj); return (Callable)result; } // name will call storeScriptable(cx, thisObj); return (Callable)nameOrFunction(cx, scope, parent, name, true); } /** * Prepare for calling obj[id](...): return function corresponding to * obj[id] and make obj properly converted to Scriptable available * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj. * The caller must call ScriptRuntime.lastStoredScriptable() immediately * after calling this method. */ public static Callable getElemFunctionAndThis(Object obj, Object elem, Context cx) { String s = toStringIdOrIndex(cx, elem); if (s != null) { return getPropFunctionAndThis(obj, s, cx); } int index = lastIndexResult(cx); Scriptable thisObj = toObjectOrNull(cx, obj); if (thisObj == null) { throw undefCallError(obj, String.valueOf(index)); } Object value; for (;;) { // Ignore XML lookup as requred by ECMA 357, 11.2.2.1 value = ScriptableObject.getProperty(thisObj, index); if (value != Scriptable.NOT_FOUND) { break; } if (!(thisObj instanceof XMLObject)) { break; } XMLObject xmlObject = (XMLObject)thisObj; Scriptable extra = xmlObject.getExtraMethodSource(cx); if (extra == null) { break; } thisObj = extra; } if (!(value instanceof Callable)) { throw notFunctionError(value, elem); } storeScriptable(cx, thisObj); return (Callable)value; } /** * Prepare for calling obj.property(...): return function corresponding to * obj.property and make obj properly converted to Scriptable available * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj. * The caller must call ScriptRuntime.lastStoredScriptable() immediately * after calling this method. */ public static Callable getPropFunctionAndThis(Object obj, String property, Context cx) { Scriptable thisObj = toObjectOrNull(cx, obj); if (thisObj == null) { throw undefCallError(obj, property); } Object value; for (;;) { // Ignore XML lookup as requred by ECMA 357, 11.2.2.1 value = ScriptableObject.getProperty(thisObj, property); if (value != Scriptable.NOT_FOUND) { break; } if (!(thisObj instanceof XMLObject)) { break; } XMLObject xmlObject = (XMLObject)thisObj; Scriptable extra = xmlObject.getExtraMethodSource(cx); if (extra == null) { break; } thisObj = extra; } if (!(value instanceof Callable)) { Object noSuchMethod = ScriptableObject.getProperty(thisObj, "__noSuchMethod__"); if (noSuchMethod instanceof Callable) value = new NoSuchMethodShim((Callable)noSuchMethod, property); else throw notFunctionError(value, property); } storeScriptable(cx, thisObj); return (Callable)value; } /** * Prepare for calling <expression>(...): return function corresponding to * <expression> and make parent scope of the function available * as ScriptRuntime.lastStoredScriptable() for consumption as thisObj. * The caller must call ScriptRuntime.lastStoredScriptable() immediately * after calling this method. */ public static Callable getValueFunctionAndThis(Object value, Context cx) { if (!(value instanceof Callable)) { throw notFunctionError(value); } Callable f = (Callable)value; Scriptable thisObj = null; if (f instanceof Scriptable) { thisObj = ((Scriptable)f).getParentScope(); } if (thisObj == null) { if (cx.topCallScope == null) throw new IllegalStateException(); thisObj = cx.topCallScope; } if (thisObj.getParentScope() != null) { if (thisObj instanceof NativeWith) { // functions defined inside with should have with target // as their thisObj } else if (thisObj instanceof NativeCall) { // nested functions should have top scope as their thisObj thisObj = ScriptableObject.getTopLevelScope(thisObj); } } storeScriptable(cx, thisObj); return f; } /** * Perform function call in reference context. Should always * return value that can be passed to * {@link #refGet(Ref, Context)} or {@link #refSet(Ref, Object, Context)} * arbitrary number of times. * The args array reference should not be stored in any object that is * can be GC-reachable after this method returns. If this is necessary, * store args.clone(), not args array itself. */ public static Ref callRef(Callable function, Scriptable thisObj, Object[] args, Context cx) { if (function instanceof RefCallable) { RefCallable rfunction = (RefCallable)function; Ref ref = rfunction.refCall(cx, thisObj, args); if (ref == null) { throw new IllegalStateException(rfunction.getClass().getName()+".refCall() returned null"); } return ref; } // No runtime support for now String msg = getMessage1("msg.no.ref.from.function", toString(function)); throw constructError("ReferenceError", msg); } /** * Operator new. * * See ECMA 11.2.2 */ public static Scriptable newObject(Object fun, Context cx, Scriptable scope, Object[] args) { if (!(fun instanceof Function)) { throw notFunctionError(fun); } Function function = (Function)fun; return function.construct(cx, scope, args); } public static Object callSpecial(Context cx, Callable fun, Scriptable thisObj, Object[] args, Scriptable scope, Scriptable callerThis, int callType, String filename, int lineNumber) { if (callType == Node.SPECIALCALL_EVAL) { if (NativeGlobal.isEvalFunction(fun)) { return evalSpecial(cx, scope, callerThis, args, filename, lineNumber); } } else if (callType == Node.SPECIALCALL_WITH) { if (NativeWith.isWithFunction(fun)) { throw Context.reportRuntimeError1("msg.only.from.new", "With"); } } else { throw Kit.codeBug(); } return fun.call(cx, scope, thisObj, args); } public static Object newSpecial(Context cx, Object fun, Object[] args, Scriptable scope, int callType) { if (callType == Node.SPECIALCALL_EVAL) { if (NativeGlobal.isEvalFunction(fun)) { throw typeError1("msg.not.ctor", "eval"); } } else if (callType == Node.SPECIALCALL_WITH) { if (NativeWith.isWithFunction(fun)) { return NativeWith.newWithSpecial(cx, scope, args); } } else { throw Kit.codeBug(); } return newObject(fun, cx, scope, args); } /** * Function.prototype.apply and Function.prototype.call * * See Ecma 15.3.4.[34] */ public static Object applyOrCall(boolean isApply, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { int L = args.length; Callable function; if (thisObj instanceof Callable) { function = (Callable)thisObj; } else { Object value = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); if (!(value instanceof Callable)) { throw ScriptRuntime.notFunctionError(value, thisObj); } function = (Callable)value; } Scriptable callThis = null; if (L != 0) { callThis = toObjectOrNull(cx, args[0]); } if (callThis == null) { // This covers the case of args[0] == (null|undefined) as well. callThis = getTopCallScope(cx); } Object[] callArgs; if (isApply) { // Follow Ecma 15.3.4.3 if (L <= 1) { callArgs = ScriptRuntime.emptyArgs; } else { Object arg1 = args[1]; if (arg1 == null || arg1 == Undefined.instance) { callArgs = ScriptRuntime.emptyArgs; } else if (arg1 instanceof NativeArray || arg1 instanceof Arguments) { callArgs = cx.getElements((Scriptable) arg1); } else { throw ScriptRuntime.typeError0("msg.arg.isnt.array"); } } } else { // Follow Ecma 15.3.4.4 if (L <= 1) { callArgs = ScriptRuntime.emptyArgs; } else { callArgs = new Object[L - 1]; System.arraycopy(args, 1, callArgs, 0, L - 1); } } return function.call(cx, scope, callThis, callArgs); } /** * The eval function property of the global object. * * See ECMA 15.1.2.1 */ public static Object evalSpecial(Context cx, Scriptable scope, Object thisArg, Object[] args, String filename, int lineNumber) { if (args.length < 1) return Undefined.instance; Object x = args[0]; if (!(x instanceof String)) { if (cx.hasFeature(Context.FEATURE_STRICT_MODE) || cx.hasFeature(Context.FEATURE_STRICT_EVAL)) { throw Context.reportRuntimeError0("msg.eval.nonstring.strict"); } String message = ScriptRuntime.getMessage0("msg.eval.nonstring"); Context.reportWarning(message); return x; } if (filename == null) { int[] linep = new int[1]; filename = Context.getSourcePositionFromStack(linep); if (filename != null) { lineNumber = linep[0]; } else { filename = ""; } } String sourceName = ScriptRuntime. makeUrlForGeneratedScript(true, filename, lineNumber); ErrorReporter reporter; reporter = DefaultErrorReporter.forEval(cx.getErrorReporter()); // Compile with explicit interpreter instance to force interpreter // mode. Script script = cx.compileString((String)x, new Interpreter(), reporter, sourceName, 1, null); ((InterpretedFunction)script).idata.evalScriptFlag = true; Callable c = (Callable)script; return c.call(cx, scope, (Scriptable)thisArg, ScriptRuntime.emptyArgs); } /** * The typeof operator */ public static String typeof(Object value) { if (value == null) return "object"; if (value == Undefined.instance) return "undefined"; if (value instanceof Scriptable) { if (value instanceof XMLObject) return "xml"; return (value instanceof Callable) ? "function" : "object"; } if (value instanceof String) return "string"; if (value instanceof Number) return "number"; if (value instanceof Boolean) return "boolean"; throw errorWithClassName("msg.invalid.type", value); } /** * The typeof operator that correctly handles the undefined case */ public static String typeofName(Scriptable scope, String id) { Context cx = Context.getContext(); Scriptable val = bind(cx, scope, id); if (val == null) return "undefined"; return typeof(getObjectProp(val, id, cx)); } // neg: // implement the '-' operator inline in the caller // as "-toNumber(val)" // not: // implement the '!' operator inline in the caller // as "!toBoolean(val)" // bitnot: // implement the '~' operator inline in the caller // as "~toInt32(val)" public static Object add(Object val1, Object val2, Context cx) { if(val1 instanceof Number && val2 instanceof Number) { return wrapNumber(((Number)val1).doubleValue() + ((Number)val2).doubleValue()); } if (val1 instanceof XMLObject) { Object test = ((XMLObject)val1).addValues(cx, true, val2); if (test != Scriptable.NOT_FOUND) { return test; } } if (val2 instanceof XMLObject) { Object test = ((XMLObject)val2).addValues(cx, false, val1); if (test != Scriptable.NOT_FOUND) { return test; } } if (val1 instanceof Scriptable) val1 = ((Scriptable) val1).getDefaultValue(null); if (val2 instanceof Scriptable) val2 = ((Scriptable) val2).getDefaultValue(null); if (!(val1 instanceof String) && !(val2 instanceof String)) if ((val1 instanceof Number) && (val2 instanceof Number)) return wrapNumber(((Number)val1).doubleValue() + ((Number)val2).doubleValue()); else return wrapNumber(toNumber(val1) + toNumber(val2)); return toString(val1).concat(toString(val2)); } /** * @deprecated The method is only present for compatibility. */ public static Object nameIncrDecr(Scriptable scopeChain, String id, int incrDecrMask) { return nameIncrDecr(scopeChain, id, Context.getContext(), incrDecrMask); } public static Object nameIncrDecr(Scriptable scopeChain, String id, Context cx, int incrDecrMask) { Scriptable target; Object value; search: { do { if (cx.useDynamicScope && scopeChain.getParentScope() == null) { scopeChain = checkDynamicScope(cx.topCallScope, scopeChain); } target = scopeChain; do { value = target.get(id, scopeChain); if (value != Scriptable.NOT_FOUND) { break search; } target = target.getPrototype(); } while (target != null); scopeChain = scopeChain.getParentScope(); } while (scopeChain != null); throw notFoundError(scopeChain, id); } return doScriptableIncrDecr(target, id, scopeChain, value, incrDecrMask); } public static Object propIncrDecr(Object obj, String id, Context cx, int incrDecrMask) { Scriptable start = toObjectOrNull(cx, obj); if (start == null) { throw undefReadError(obj, id); } Scriptable target = start; Object value; search: { do { value = target.get(id, start); if (value != Scriptable.NOT_FOUND) { break search; } target = target.getPrototype(); } while (target != null); start.put(id, start, NaNobj); return NaNobj; } return doScriptableIncrDecr(target, id, start, value, incrDecrMask); } private static Object doScriptableIncrDecr(Scriptable target, String id, Scriptable protoChainStart, Object value, int incrDecrMask) { boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); double number; if (value instanceof Number) { number = ((Number)value).doubleValue(); } else { number = toNumber(value); if (post) { // convert result to number value = wrapNumber(number); } } if ((incrDecrMask & Node.DECR_FLAG) == 0) { ++number; } else { --number; } Number result = wrapNumber(number); target.put(id, protoChainStart, result); if (post) { return value; } else { return result; } } public static Object elemIncrDecr(Object obj, Object index, Context cx, int incrDecrMask) { Object value = getObjectElem(obj, index, cx); boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); double number; if (value instanceof Number) { number = ((Number)value).doubleValue(); } else { number = toNumber(value); if (post) { // convert result to number value = wrapNumber(number); } } if ((incrDecrMask & Node.DECR_FLAG) == 0) { ++number; } else { --number; } Number result = wrapNumber(number); setObjectElem(obj, index, result, cx); if (post) { return value; } else { return result; } } public static Object refIncrDecr(Ref ref, Context cx, int incrDecrMask) { Object value = ref.get(cx); boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); double number; if (value instanceof Number) { number = ((Number)value).doubleValue(); } else { number = toNumber(value); if (post) { // convert result to number value = wrapNumber(number); } } if ((incrDecrMask & Node.DECR_FLAG) == 0) { ++number; } else { --number; } Number result = wrapNumber(number); ref.set(cx, result); if (post) { return value; } else { return result; } } private static Object toPrimitive(Object val) { if (!(val instanceof Scriptable)) { return val; } Scriptable s = (Scriptable)val; Object result = s.getDefaultValue(null); if (result instanceof Scriptable) throw typeError0("msg.bad.default.value"); return result; } /** * Equality * * See ECMA 11.9 */ public static boolean eq(Object x, Object y) { if (x == null || x == Undefined.instance) { if (y == null || y == Undefined.instance) { return true; } if (y instanceof ScriptableObject) { Object test = ((ScriptableObject)y).equivalentValues(x); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } return false; } else if (x instanceof Number) { return eqNumber(((Number)x).doubleValue(), y); } else if (x instanceof String) { return eqString((String)x, y); } else if (x instanceof Boolean) { boolean b = ((Boolean)x).booleanValue(); if (y instanceof Boolean) { return b == ((Boolean)y).booleanValue(); } if (y instanceof ScriptableObject) { Object test = ((ScriptableObject)y).equivalentValues(x); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } return eqNumber(b ? 1.0 : 0.0, y); } else if (x instanceof Scriptable) { if (y instanceof Scriptable) { if (x == y) { return true; } if (x instanceof ScriptableObject) { Object test = ((ScriptableObject)x).equivalentValues(y); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } if (y instanceof ScriptableObject) { Object test = ((ScriptableObject)y).equivalentValues(x); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } if (x instanceof Wrapper && y instanceof Wrapper) { return ((Wrapper)x).unwrap() == ((Wrapper)y).unwrap(); } return false; } else if (y instanceof Boolean) { if (x instanceof ScriptableObject) { Object test = ((ScriptableObject)x).equivalentValues(y); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } double d = ((Boolean)y).booleanValue() ? 1.0 : 0.0; return eqNumber(d, x); } else if (y instanceof Number) { return eqNumber(((Number)y).doubleValue(), x); } else if (y instanceof String) { return eqString((String)y, x); } // covers the case when y == Undefined.instance as well return false; } else { warnAboutNonJSObject(x); return x == y; } } static boolean eqNumber(double x, Object y) { for (;;) { if (y == null || y == Undefined.instance) { return false; } else if (y instanceof Number) { return x == ((Number)y).doubleValue(); } else if (y instanceof String) { return x == toNumber(y); } else if (y instanceof Boolean) { return x == (((Boolean)y).booleanValue() ? 1.0 : +0.0); } else if (y instanceof Scriptable) { if (y instanceof ScriptableObject) { Object xval = wrapNumber(x); Object test = ((ScriptableObject)y).equivalentValues(xval); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } y = toPrimitive(y); } else { warnAboutNonJSObject(y); return false; } } } private static boolean eqString(String x, Object y) { for (;;) { if (y == null || y == Undefined.instance) { return false; } else if (y instanceof String) { return x.equals(y); } else if (y instanceof Number) { return toNumber(x) == ((Number)y).doubleValue(); } else if (y instanceof Boolean) { return toNumber(x) == (((Boolean)y).booleanValue() ? 1.0 : 0.0); } else if (y instanceof Scriptable) { if (y instanceof ScriptableObject) { Object test = ((ScriptableObject)y).equivalentValues(x); if (test != Scriptable.NOT_FOUND) { return ((Boolean)test).booleanValue(); } } y = toPrimitive(y); continue; } else { warnAboutNonJSObject(y); return false; } } } public static boolean shallowEq(Object x, Object y) { if (x == y) { if (!(x instanceof Number)) { return true; } // NaN check double d = ((Number)x).doubleValue(); return d == d; } if (x == null || x == Undefined.instance) { return false; } else if (x instanceof Number) { if (y instanceof Number) { return ((Number)x).doubleValue() == ((Number)y).doubleValue(); } } else if (x instanceof String) { if (y instanceof String) { return x.equals(y); } } else if (x instanceof Boolean) { if (y instanceof Boolean) { return x.equals(y); } } else if (x instanceof Scriptable) { if (x instanceof Wrapper && y instanceof Wrapper) { return ((Wrapper)x).unwrap() == ((Wrapper)y).unwrap(); } } else { warnAboutNonJSObject(x); return x == y; } return false; } /** * The instanceof operator. * * @return a instanceof b */ public static boolean instanceOf(Object a, Object b, Context cx) { // Check RHS is an object if (! (b instanceof Scriptable)) { throw typeError0("msg.instanceof.not.object"); } // for primitive values on LHS, return false // XXX we may want to change this so that // 5 instanceof Number == true if (! (a instanceof Scriptable)) return false; return ((Scriptable)b).hasInstance((Scriptable)a); } /** * Delegates to * * @return true iff rhs appears in lhs' proto chain */ public static boolean jsDelegatesTo(Scriptable lhs, Scriptable rhs) { Scriptable proto = lhs.getPrototype(); while (proto != null) { if (proto.equals(rhs)) return true; proto = proto.getPrototype(); } return false; } /** * The in operator. * * This is a new JS 1.3 language feature. The in operator mirrors * the operation of the for .. in construct, and tests whether the * rhs has the property given by the lhs. It is different from the * for .. in construct in that: * <BR> - it doesn't perform ToObject on the right hand side * <BR> - it returns true for DontEnum properties. * @param a the left hand operand * @param b the right hand operand * * @return true if property name or element number a is a property of b */ public static boolean in(Object a, Object b, Context cx) { if (!(b instanceof Scriptable)) { throw typeError0("msg.instanceof.not.object"); } return hasObjectElem((Scriptable)b, a, cx); } public static boolean cmp_LT(Object val1, Object val2) { double d1, d2; if (val1 instanceof Number && val2 instanceof Number) { d1 = ((Number)val1).doubleValue(); d2 = ((Number)val2).doubleValue(); } else { if (val1 instanceof Scriptable) val1 = ((Scriptable) val1).getDefaultValue(NumberClass); if (val2 instanceof Scriptable) val2 = ((Scriptable) val2).getDefaultValue(NumberClass); if (val1 instanceof String && val2 instanceof String) { return ((String)val1).compareTo((String)val2) < 0; } d1 = toNumber(val1); d2 = toNumber(val2); } return d1 < d2; } public static boolean cmp_LE(Object val1, Object val2) { double d1, d2; if (val1 instanceof Number && val2 instanceof Number) { d1 = ((Number)val1).doubleValue(); d2 = ((Number)val2).doubleValue(); } else { if (val1 instanceof Scriptable) val1 = ((Scriptable) val1).getDefaultValue(NumberClass); if (val2 instanceof Scriptable) val2 = ((Scriptable) val2).getDefaultValue(NumberClass); if (val1 instanceof String && val2 instanceof String) { return ((String)val1).compareTo((String)val2) <= 0; } d1 = toNumber(val1); d2 = toNumber(val2); } return d1 <= d2; } // ------------------ // Statements // ------------------ public static ScriptableObject getGlobal(Context cx) { final String GLOBAL_CLASS = "org.mozilla.javascript.tools.shell.Global"; Class globalClass = Kit.classOrNull(GLOBAL_CLASS); if (globalClass != null) { try { Class[] parm = { ScriptRuntime.ContextClass }; Constructor globalClassCtor = globalClass.getConstructor(parm); Object[] arg = { cx }; return (ScriptableObject) globalClassCtor.newInstance(arg); } catch (Exception e) { // fall through... } } return new ImporterTopLevel(cx); } public static boolean hasTopCall(Context cx) { return (cx.topCallScope != null); } public static Scriptable getTopCallScope(Context cx) { Scriptable scope = cx.topCallScope; if (scope == null) { throw new IllegalStateException(); } return scope; } public static Object doTopCall(Callable callable, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { if (scope == null) throw new IllegalArgumentException(); if (cx.topCallScope != null) throw new IllegalStateException(); Object result; cx.topCallScope = ScriptableObject.getTopLevelScope(scope); cx.useDynamicScope = cx.hasFeature(Context.FEATURE_DYNAMIC_SCOPE); ContextFactory f = cx.getFactory(); try { result = f.doTopCall(callable, cx, scope, thisObj, args); } finally { cx.topCallScope = null; // Cleanup cached references cx.cachedXMLLib = null; if (cx.currentActivationCall != null) { // Function should always call exitActivationFunction // if it creates activation record throw new IllegalStateException(); } } return result; } /** * Return <tt>possibleDynamicScope</tt> if <tt>staticTopScope</tt> * is present on its prototype chain and return <tt>staticTopScope</tt> * otherwise. * Should only be called when <tt>staticTopScope</tt> is top scope. */ static Scriptable checkDynamicScope(Scriptable possibleDynamicScope, Scriptable staticTopScope) { // Return cx.topCallScope if scope if (possibleDynamicScope == staticTopScope) { return possibleDynamicScope; } Scriptable proto = possibleDynamicScope; for (;;) { proto = proto.getPrototype(); if (proto == staticTopScope) { return possibleDynamicScope; } if (proto == null) { return staticTopScope; } } } public static void initScript(NativeFunction funObj, Scriptable thisObj, Context cx, Scriptable scope, boolean evalScript) { if (cx.topCallScope == null) throw new IllegalStateException(); int varCount = funObj.getParamAndVarCount(); if (varCount != 0) { Scriptable varScope = scope; // Never define any variables from var statements inside with // object. See bug 38590. while (varScope instanceof NativeWith) { varScope = varScope.getParentScope(); } for (int i = varCount; i-- != 0;) { String name = funObj.getParamOrVarName(i); boolean isConst = funObj.getParamOrVarConst(i); // Don't overwrite existing def if already defined in object // or prototypes of object. if (!ScriptableObject.hasProperty(scope, name)) { if (!evalScript) { // Global var definitions are supposed to be DONTDELETE if (isConst) ScriptableObject.defineConstProperty(varScope, name); else ScriptableObject.defineProperty( varScope, name, Undefined.instance, ScriptableObject.PERMANENT); } else { varScope.put(name, varScope, Undefined.instance); } } else { ScriptableObject.redefineProperty(scope, name, isConst); } } } } public static Scriptable createFunctionActivation(NativeFunction funObj, Scriptable scope, Object[] args) { return new NativeCall(funObj, scope, args); } public static void enterActivationFunction(Context cx, Scriptable scope) { if (cx.topCallScope == null) throw new IllegalStateException(); NativeCall call = (NativeCall)scope; call.parentActivationCall = cx.currentActivationCall; cx.currentActivationCall = call; } public static void exitActivationFunction(Context cx) { NativeCall call = cx.currentActivationCall; cx.currentActivationCall = call.parentActivationCall; call.parentActivationCall = null; } static NativeCall findFunctionActivation(Context cx, Function f) { NativeCall call = cx.currentActivationCall; while (call != null) { if (call.function == f) return call; call = call.parentActivationCall; } return null; } public static Scriptable newCatchScope(Throwable t, Scriptable lastCatchScope, String exceptionName, Context cx, Scriptable scope) { Object obj; boolean cacheObj; getObj: if (t instanceof JavaScriptException) { cacheObj = false; obj = ((JavaScriptException)t).getValue(); } else { cacheObj = true; // Create wrapper object unless it was associated with // the previous scope object if (lastCatchScope != null) { NativeObject last = (NativeObject)lastCatchScope; obj = last.getAssociatedValue(t); if (obj == null) Kit.codeBug(); break getObj; } RhinoException re; String errorName; String errorMsg; Throwable javaException = null; if (t instanceof EcmaError) { EcmaError ee = (EcmaError)t; re = ee; errorName = ee.getName(); errorMsg = ee.getErrorMessage(); } else if (t instanceof WrappedException) { WrappedException we = (WrappedException)t; re = we; javaException = we.getWrappedException(); errorName = "JavaException"; errorMsg = javaException.getClass().getName() +": "+javaException.getMessage(); } else if (t instanceof EvaluatorException) { // Pure evaluator exception, nor WrappedException instance EvaluatorException ee = (EvaluatorException)t; re = ee; errorName = "InternalError"; errorMsg = ee.getMessage(); } else { // Script can catch only instances of JavaScriptException, // EcmaError and EvaluatorException throw Kit.codeBug(); } String sourceUri = re.sourceName(); if (sourceUri == null) { sourceUri = ""; } int line = re.lineNumber(); Object args[]; if (line > 0) { args = new Object[] { errorMsg, sourceUri, new Integer(line) }; } else { args = new Object[] { errorMsg, sourceUri }; } Scriptable errorObject = cx.newObject(scope, errorName, args); ScriptableObject.putProperty(errorObject, "name", errorName); if (javaException != null) { Object wrap = cx.getWrapFactory().wrap(cx, scope, javaException, null); ScriptableObject.defineProperty( errorObject, "javaException", wrap, ScriptableObject.PERMANENT | ScriptableObject.READONLY); } Object wrap = cx.getWrapFactory().wrap(cx, scope, re, null); ScriptableObject.defineProperty( errorObject, "rhinoException", wrap, ScriptableObject.PERMANENT | ScriptableObject.READONLY); obj = errorObject; } NativeObject catchScopeObject = new NativeObject(); // See ECMA 12.4 catchScopeObject.defineProperty( exceptionName, obj, ScriptableObject.PERMANENT); // Add special Rhino object __exception__ defined in the catch // scope that can be used to retrieve the Java exception associated // with the JavaScript exception (to get stack trace info, etc.) catchScopeObject.defineProperty( "__exception__", Context.javaToJS(t, catchScopeObject), ScriptableObject.PERMANENT|ScriptableObject.DONTENUM); if (cacheObj) { catchScopeObject.associateValue(t, obj); } return catchScopeObject; } public static Scriptable enterWith(Object obj, Context cx, Scriptable scope) { Scriptable sobj = toObjectOrNull(cx, obj); if (sobj == null) { throw typeError1("msg.undef.with", toString(obj)); } if (sobj instanceof XMLObject) { XMLObject xmlObject = (XMLObject)sobj; return xmlObject.enterWith(scope); } return new NativeWith(scope, sobj); } public static Scriptable leaveWith(Scriptable scope) { NativeWith nw = (NativeWith)scope; return nw.getParentScope(); } public static Scriptable enterDotQuery(Object value, Scriptable scope) { if (!(value instanceof XMLObject)) { throw notXmlError(value); } XMLObject object = (XMLObject)value; return object.enterDotQuery(scope); } public static Object updateDotQuery(boolean value, Scriptable scope) { // Return null to continue looping NativeWith nw = (NativeWith)scope; return nw.updateDotQuery(value); } public static Scriptable leaveDotQuery(Scriptable scope) { NativeWith nw = (NativeWith)scope; return nw.getParentScope(); } public static void setFunctionProtoAndParent(BaseFunction fn, Scriptable scope) { fn.setParentScope(scope); fn.setPrototype(ScriptableObject.getFunctionPrototype(scope)); } public static void setObjectProtoAndParent(ScriptableObject object, Scriptable scope) { // Compared with function it always sets the scope to top scope scope = ScriptableObject.getTopLevelScope(scope); object.setParentScope(scope); Scriptable proto = ScriptableObject.getClassPrototype(scope, object.getClassName()); object.setPrototype(proto); } public static void initFunction(Context cx, Scriptable scope, NativeFunction function, int type, boolean fromEvalCode) { if (type == FunctionNode.FUNCTION_STATEMENT) { String name = function.getFunctionName(); if (name != null && name.length() != 0) { if (!fromEvalCode) { // ECMA specifies that functions defined in global and // function scope outside eval should have DONTDELETE set. ScriptableObject.defineProperty (scope, name, function, ScriptableObject.PERMANENT); } else { scope.put(name, scope, function); } } } else if (type == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) { String name = function.getFunctionName(); if (name != null && name.length() != 0) { // Always put function expression statements into initial // activation object ignoring the with statement to follow // SpiderMonkey while (scope instanceof NativeWith) { scope = scope.getParentScope(); } scope.put(name, scope, function); } } else { throw Kit.codeBug(); } } public static Scriptable newArrayLiteral(Object[] objects, int[] skipIndexces, Context cx, Scriptable scope) { int count = objects.length; int skipCount = 0; if (skipIndexces != null) { skipCount = skipIndexces.length; } int length = count + skipCount; Integer lengthObj = new Integer(length); Scriptable arrayObj; /* * If the version is 120, then new Array(4) means create a new * array with 4 as the first element. In this case, we have to * set length property manually. */ if (cx.getLanguageVersion() == Context.VERSION_1_2) { arrayObj = cx.newObject(scope, "Array", ScriptRuntime.emptyArgs); ScriptableObject.putProperty(arrayObj, "length", lengthObj); } else { arrayObj = cx.newObject(scope, "Array", new Object[] { lengthObj }); } int skip = 0; for (int i = 0, j = 0; i != length; ++i) { if (skip != skipCount && skipIndexces[skip] == i) { ++skip; continue; } ScriptableObject.putProperty(arrayObj, i, objects[j]); ++j; } return arrayObj; } /** * This method is here for backward compat with existing compiled code. It * is called when an object literal is compiled. The next instance will be * the version called from new code. * @deprecated This method only present for compatibility. */ public static Scriptable newObjectLiteral(Object[] propertyIds, Object[] propertyValues, Context cx, Scriptable scope) { // This will initialize to all zeros, exactly what we need for old-style // getterSetters values (no getters or setters in the list) int [] getterSetters = new int[propertyIds.length]; return newObjectLiteral(propertyIds, propertyValues, getterSetters, cx, scope); } public static Scriptable newObjectLiteral(Object[] propertyIds, Object[] propertyValues, int [] getterSetters, Context cx, Scriptable scope) { Scriptable object = cx.newObject(scope); for (int i = 0, end = propertyIds.length; i != end; ++i) { Object id = propertyIds[i]; int getterSetter = getterSetters[i]; Object value = propertyValues[i]; if (id instanceof String) { if (getterSetter == 0) ScriptableObject.putProperty(object, (String)id, value); else { Callable fun; String definer; if (getterSetter < 0) // < 0 means get foo() ... definer = "__defineGetter__"; else definer = "__defineSetter__"; fun = getPropFunctionAndThis(object, definer, cx); // Must consume the last scriptable object in cx lastStoredScriptable(cx); Object[] outArgs = new Object[2]; outArgs[0] = id; outArgs[1] = value; fun.call(cx, scope, object, outArgs); } } else { int index = ((Integer)id).intValue(); ScriptableObject.putProperty(object, index, value); } } return object; } public static boolean isArrayObject(Object obj) { return obj instanceof NativeArray || obj instanceof Arguments; } public static Object[] getArrayElements(Scriptable object) { Context cx = Context.getContext(); long longLen = NativeArray.getLengthProperty(cx, object); if (longLen > Integer.MAX_VALUE) { // arrays beyond MAX_INT is not in Java in any case throw new IllegalArgumentException(); } int len = (int) longLen; if (len == 0) { return ScriptRuntime.emptyArgs; } else { Object[] result = new Object[len]; for (int i=0; i < len; i++) { Object elem = ScriptableObject.getProperty(object, i); result[i] = (elem == Scriptable.NOT_FOUND) ? Undefined.instance : elem; } return result; } } static void checkDeprecated(Context cx, String name) { int version = cx.getLanguageVersion(); if (version >= Context.VERSION_1_4 || version == Context.VERSION_DEFAULT) { String msg = getMessage1("msg.deprec.ctor", name); if (version == Context.VERSION_DEFAULT) Context.reportWarning(msg); else throw Context.reportRuntimeError(msg); } } public static String getMessage0(String messageId) { return getMessage(messageId, null); } public static String getMessage1(String messageId, Object arg1) { Object[] arguments = {arg1}; return getMessage(messageId, arguments); } public static String getMessage2( String messageId, Object arg1, Object arg2) { Object[] arguments = {arg1, arg2}; return getMessage(messageId, arguments); } public static String getMessage3( String messageId, Object arg1, Object arg2, Object arg3) { Object[] arguments = {arg1, arg2, arg3}; return getMessage(messageId, arguments); } public static String getMessage4( String messageId, Object arg1, Object arg2, Object arg3, Object arg4) { Object[] arguments = {arg1, arg2, arg3, arg4}; return getMessage(messageId, arguments); } /* OPT there's a noticable delay for the first error! Maybe it'd * make sense to use a ListResourceBundle instead of a properties * file to avoid (synchronized) text parsing. */ public static String getMessage(String messageId, Object[] arguments) { final String defaultResource = "org.mozilla.javascript.resources.Messages"; Context cx = Context.getCurrentContext(); Locale locale = cx != null ? cx.getLocale() : Locale.getDefault(); // ResourceBundle does cacheing. ResourceBundle rb = ResourceBundle.getBundle(defaultResource, locale); String formatString; try { formatString = rb.getString(messageId); } catch (java.util.MissingResourceException mre) { throw new RuntimeException ("no message resource found for message property "+ messageId); } /* * It's OK to format the string, even if 'arguments' is null; * we need to format it anyway, to make double ''s collapse to * single 's. */ // TODO: MessageFormat is not available on pJava MessageFormat formatter = new MessageFormat(formatString); return formatter.format(arguments); } public static EcmaError constructError(String error, String message) { int[] linep = new int[1]; String filename = Context.getSourcePositionFromStack(linep); return constructError(error, message, filename, linep[0], null, 0); } public static EcmaError constructError(String error, String message, String sourceName, int lineNumber, String lineSource, int columnNumber) { return new EcmaError(error, message, sourceName, lineNumber, lineSource, columnNumber); } public static EcmaError typeError(String message) { return constructError("TypeError", message); } public static EcmaError typeError0(String messageId) { String msg = getMessage0(messageId); return typeError(msg); } public static EcmaError typeError1(String messageId, String arg1) { String msg = getMessage1(messageId, arg1); return typeError(msg); } public static EcmaError typeError2(String messageId, String arg1, String arg2) { String msg = getMessage2(messageId, arg1, arg2); return typeError(msg); } public static EcmaError typeError3(String messageId, String arg1, String arg2, String arg3) { String msg = getMessage3(messageId, arg1, arg2, arg3); return typeError(msg); } public static RuntimeException undefReadError(Object object, Object id) { String idStr = (id == null) ? "null" : id.toString(); return typeError2("msg.undef.prop.read", toString(object), idStr); } public static RuntimeException undefCallError(Object object, Object id) { String idStr = (id == null) ? "null" : id.toString(); return typeError2("msg.undef.method.call", toString(object), idStr); } public static RuntimeException undefWriteError(Object object, Object id, Object value) { String idStr = (id == null) ? "null" : id.toString(); String valueStr = (value instanceof Scriptable) ? value.toString() : toString(value); return typeError3("msg.undef.prop.write", toString(object), idStr, valueStr); } public static RuntimeException notFoundError(Scriptable object, String property) { // XXX: use object to improve the error message String msg = getMessage1("msg.is.not.defined", property); throw constructError("ReferenceError", msg); } public static RuntimeException notFunctionError(Object value) { return notFunctionError(value, value); } public static RuntimeException notFunctionError(Object value, Object messageHelper) { // XXX Use value for better error reporting String msg = (messageHelper == null) ? "null" : messageHelper.toString(); if (value == Scriptable.NOT_FOUND) { return typeError1("msg.function.not.found", msg); } return typeError2("msg.isnt.function", msg, value == null ? "null" : value.getClass().getName()); } private static RuntimeException notXmlError(Object value) { throw typeError1("msg.isnt.xml.object", ScriptRuntime.toString(value)); } private static void warnAboutNonJSObject(Object nonJSObject) { String message = "RHINO USAGE WARNING: Missed Context.javaToJS() conversion:\n" +"Rhino runtime detected object "+nonJSObject+" of class "+nonJSObject.getClass().getName()+" where it expected String, Number, Boolean or Scriptable instance. Please check your code for missing Context.javaToJS() call."; Context.reportWarning(message); // Just to be sure that it would be noticed System.err.println(message); } public static RegExpProxy getRegExpProxy(Context cx) { return cx.getRegExpProxy(); } public static void setRegExpProxy(Context cx, RegExpProxy proxy) { if (proxy == null) throw new IllegalArgumentException(); cx.regExpProxy = proxy; } public static RegExpProxy checkRegExpProxy(Context cx) { RegExpProxy result = getRegExpProxy(cx); if (result == null) { throw Context.reportRuntimeError0("msg.no.regexp"); } return result; } private static XMLLib currentXMLLib(Context cx) { // Scripts should be running to access this if (cx.topCallScope == null) throw new IllegalStateException(); XMLLib xmlLib = cx.cachedXMLLib; if (xmlLib == null) { xmlLib = XMLLib.extractFromScope(cx.topCallScope); if (xmlLib == null) throw new IllegalStateException(); cx.cachedXMLLib = xmlLib; } return xmlLib; } /** * Escapes the reserved characters in a value of an attribute * * @param value Unescaped text * @return The escaped text */ public static String escapeAttributeValue(Object value, Context cx) { XMLLib xmlLib = currentXMLLib(cx); return xmlLib.escapeAttributeValue(value); } /** * Escapes the reserved characters in a value of a text node * * @param value Unescaped text * @return The escaped text */ public static String escapeTextValue(Object value, Context cx) { XMLLib xmlLib = currentXMLLib(cx); return xmlLib.escapeTextValue(value); } public static Ref memberRef(Object obj, Object elem, Context cx, int memberTypeFlags) { if (!(obj instanceof XMLObject)) { throw notXmlError(obj); } XMLObject xmlObject = (XMLObject)obj; return xmlObject.memberRef(cx, elem, memberTypeFlags); } public static Ref memberRef(Object obj, Object namespace, Object elem, Context cx, int memberTypeFlags) { if (!(obj instanceof XMLObject)) { throw notXmlError(obj); } XMLObject xmlObject = (XMLObject)obj; return xmlObject.memberRef(cx, namespace, elem, memberTypeFlags); } public static Ref nameRef(Object name, Context cx, Scriptable scope, int memberTypeFlags) { XMLLib xmlLib = currentXMLLib(cx); return xmlLib.nameRef(cx, name, scope, memberTypeFlags); } public static Ref nameRef(Object namespace, Object name, Context cx, Scriptable scope, int memberTypeFlags) { XMLLib xmlLib = currentXMLLib(cx); return xmlLib.nameRef(cx, namespace, name, scope, memberTypeFlags); } private static void storeIndexResult(Context cx, int index) { cx.scratchIndex = index; } static int lastIndexResult(Context cx) { return cx.scratchIndex; } public static void storeUint32Result(Context cx, long value) { if ((value >>> 32) != 0) throw new IllegalArgumentException(); cx.scratchUint32 = value; } public static long lastUint32Result(Context cx) { long value = cx.scratchUint32; if ((value >>> 32) != 0) throw new IllegalStateException(); return value; } private static void storeScriptable(Context cx, Scriptable value) { // The previosly stored scratchScriptable should be consumed if (cx.scratchScriptable != null) throw new IllegalStateException(); cx.scratchScriptable = value; } public static Scriptable lastStoredScriptable(Context cx) { Scriptable result = cx.scratchScriptable; cx.scratchScriptable = null; return result; } static String makeUrlForGeneratedScript (boolean isEval, String masterScriptUrl, int masterScriptLine) { if (isEval) { return masterScriptUrl+'#'+masterScriptLine+"(eval)"; } else { return masterScriptUrl+'#'+masterScriptLine+"(Function)"; } } static boolean isGeneratedScript(String sourceUrl) { // ALERT: this may clash with a valid URL containing (eval) or // (Function) return sourceUrl.indexOf("(eval)") >= 0 || sourceUrl.indexOf("(Function)") >= 0; } private static RuntimeException errorWithClassName(String msg, Object val) { return Context.reportRuntimeError1(msg, val.getClass().getName()); } public static final Object[] emptyArgs = new Object[0]; public static final String[] emptyStrings = new String[0]; }