org.mozilla.javascript.ScriptRuntime Maven / Gradle / Ivy
Show all versions of rhino Show documentation
/* -*- 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;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.function.BiConsumer;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.v8dtoa.DoubleConversion;
import org.mozilla.javascript.v8dtoa.FastDtoa;
import org.mozilla.javascript.xml.XMLLib;
import org.mozilla.javascript.xml.XMLObject;
/**
* This is the class that implements the runtime.
*
* @author Norris Boyd
*/
public class ScriptRuntime {
/** No instances should be created. */
protected ScriptRuntime() {}
/**
* Returns representation of the [[ThrowTypeError]] object. See ECMA 5 spec, 13.2.3
*
* @deprecated {@link #typeErrorThrower(Context)}
*/
@Deprecated
public static BaseFunction typeErrorThrower() {
return typeErrorThrower(Context.getCurrentContext());
}
/** Returns representation of the [[ThrowTypeError]] object. See ECMA 5 spec, 13.2.3 */
public static BaseFunction typeErrorThrower(Context cx) {
if (cx.typeErrorThrower == null) {
BaseFunction thrower =
new BaseFunction() {
private static final long serialVersionUID = -5891740962154902286L;
@Override
public Object call(
Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
throw typeErrorById("msg.op.not.allowed");
}
@Override
public int getLength() {
return 0;
}
};
ScriptRuntime.setFunctionProtoAndParent(thrower, cx, cx.topCallScope, false);
thrower.preventExtensions();
cx.typeErrorThrower = thrower;
}
return cx.typeErrorThrower;
}
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 this
object
* @param args the array of arguments
* @return the result of the call
*/
@Override
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 until
* execution time (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 static final 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"),
BigIntegerClass = Kit.classOrNull("java.math.BigInteger");
public static final Class> ContextClass = Kit.classOrNull("org.mozilla.javascript.Context"),
ContextFactoryClass = Kit.classOrNull("org.mozilla.javascript.ContextFactory"),
FunctionClass = Kit.classOrNull("org.mozilla.javascript.Function"),
ScriptableObjectClass = Kit.classOrNull("org.mozilla.javascript.ScriptableObject");
public static final Class ScriptableClass = Scriptable.class;
private static final Object LIBRARY_SCOPE_KEY = "LIBRARY_SCOPE";
public static boolean isRhinoRuntimeType(Class> cl) {
if (cl.isPrimitive()) {
return (cl != Character.TYPE);
}
return (cl == StringClass
|| cl == BooleanClass
|| NumberClass.isAssignableFrom(cl)
|| ScriptableClass.isAssignableFrom(cl));
}
public static ScriptableObject initSafeStandardObjects(
Context cx, ScriptableObject scope, boolean sealed) {
if (scope == null) {
scope = new NativeObject();
} else if (scope instanceof TopLevel) {
((TopLevel) scope).clearCache();
}
scope.associateValue(LIBRARY_SCOPE_KEY, scope);
(new ClassCache()).associate(scope);
BaseFunction.init(cx, scope, sealed);
NativeObject.init(scope, sealed);
Scriptable objectProto = ScriptableObject.getObjectPrototype(scope);
// Function.prototype.__proto__ should be Object.prototype
Scriptable functionProto = ScriptableObject.getClassPrototype(scope, "Function");
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(cx, scope, sealed);
if (cx.getOptimizationLevel() > 0) {
// When optimizing, attempt to fulfill all requests for new Array(N)
// with a higher threshold before switching to a sparse
// representation
NativeArray.setMaximumInitialCapacity(200000);
}
NativeString.init(scope, sealed);
NativeBoolean.init(scope, sealed);
NativeNumber.init(scope, sealed);
NativeDate.init(scope, sealed);
NativeMath.init(scope, sealed);
NativeJSON.init(scope, sealed);
NativeWith.init(scope, sealed);
NativeCall.init(scope, sealed);
NativeScript.init(cx, scope, sealed);
NativeIterator.init(cx, scope, sealed); // Also initializes NativeGenerator & ES6Generator
NativeArrayIterator.init(scope, sealed);
NativeStringIterator.init(scope, sealed);
NativeJavaObject.init(scope, sealed);
NativeJavaMap.init(scope, sealed);
boolean withXml =
cx.hasFeature(Context.FEATURE_E4X) && cx.getE4xImplementationFactory() != null;
// define lazy-loaded properties using their class name
new LazilyLoadedCtor(
scope, "RegExp", "org.mozilla.javascript.regexp.NativeRegExp", sealed, true);
new LazilyLoadedCtor(
scope, "Continuation", "org.mozilla.javascript.NativeContinuation", sealed, true);
if (withXml) {
String xmlImpl = cx.getE4xImplementationFactory().getImplementationClassName();
new LazilyLoadedCtor(scope, "XML", xmlImpl, sealed, true);
new LazilyLoadedCtor(scope, "XMLList", xmlImpl, sealed, true);
new LazilyLoadedCtor(scope, "Namespace", xmlImpl, sealed, true);
new LazilyLoadedCtor(scope, "QName", xmlImpl, sealed, true);
}
if (((cx.getLanguageVersion() >= Context.VERSION_1_8)
&& cx.hasFeature(Context.FEATURE_V8_EXTENSIONS))
|| (cx.getLanguageVersion() >= Context.VERSION_ES6)) {
new LazilyLoadedCtor(
scope,
"ArrayBuffer",
"org.mozilla.javascript.typedarrays.NativeArrayBuffer",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Int8Array",
"org.mozilla.javascript.typedarrays.NativeInt8Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Uint8Array",
"org.mozilla.javascript.typedarrays.NativeUint8Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Uint8ClampedArray",
"org.mozilla.javascript.typedarrays.NativeUint8ClampedArray",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Int16Array",
"org.mozilla.javascript.typedarrays.NativeInt16Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Uint16Array",
"org.mozilla.javascript.typedarrays.NativeUint16Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Int32Array",
"org.mozilla.javascript.typedarrays.NativeInt32Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Uint32Array",
"org.mozilla.javascript.typedarrays.NativeUint32Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Float32Array",
"org.mozilla.javascript.typedarrays.NativeFloat32Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"Float64Array",
"org.mozilla.javascript.typedarrays.NativeFloat64Array",
sealed,
true);
new LazilyLoadedCtor(
scope,
"DataView",
"org.mozilla.javascript.typedarrays.NativeDataView",
sealed,
true);
}
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
NativeSymbol.init(cx, scope, sealed);
NativeCollectionIterator.init(scope, NativeSet.ITERATOR_TAG, sealed);
NativeCollectionIterator.init(scope, NativeMap.ITERATOR_TAG, sealed);
NativeMap.init(cx, scope, sealed);
NativePromise.init(cx, scope, sealed);
NativeSet.init(cx, scope, sealed);
NativeWeakMap.init(scope, sealed);
NativeWeakSet.init(scope, sealed);
NativeBigInt.init(scope, sealed);
}
if (scope instanceof TopLevel) {
((TopLevel) scope).cacheBuiltins(scope, sealed);
}
return scope;
}
public static ScriptableObject initStandardObjects(
Context cx, ScriptableObject scope, boolean sealed) {
ScriptableObject s = initSafeStandardObjects(cx, scope, sealed);
new LazilyLoadedCtor(
s, "Packages", "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
new LazilyLoadedCtor(
s, "getClass", "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
new LazilyLoadedCtor(s, "JavaAdapter", "org.mozilla.javascript.JavaAdapter", sealed, true);
new LazilyLoadedCtor(
s, "JavaImporter", "org.mozilla.javascript.ImporterTopLevel", sealed, true);
for (String packageName : getTopPackageNames()) {
new LazilyLoadedCtor(
s, packageName, "org.mozilla.javascript.NativeJavaTopPackage", sealed, true);
}
return s;
}
static String[] getTopPackageNames() {
// Include "android" top package if running on Android
return "Dalvik".equals(System.getProperty("java.vm.name"))
? new String[] {"java", "javax", "org", "com", "edu", "net", "android"}
: new String[] {"java", "javax", "org", "com", "edu", "net"};
}
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 isJSWhitespaceOrLineTerminator(int c) {
return (isStrWhiteSpaceChar(c) || isJSLineTerminator(c));
}
/**
* Indicates if the character is a Str whitespace char according to ECMA spec: StrWhiteSpaceChar
* :::
*/
static boolean isStrWhiteSpaceChar(int c) {
switch (c) {
case ' ': //
case '\n': //
case '\r': //
case '\t': //
case '\u00A0': //
case '\u000C': //
case '\u000B': //
case '\u2028': //
case '\u2029': //
case '\uFEFF': //
return true;
default:
return Character.getType(c) == Character.SPACE_SEPARATOR;
}
}
public static Boolean wrapBoolean(boolean b) {
return Boolean.valueOf(b);
}
public static Integer wrapInt(int i) {
return Integer.valueOf(i);
}
public static Number wrapNumber(double x) {
if (Double.isNaN(x)) {
return ScriptRuntime.NaNobj;
}
return Double.valueOf(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 || Undefined.isUndefined(val)) return false;
if (val instanceof CharSequence) return ((CharSequence) val).length() != 0;
if (val instanceof BigInteger) {
return !BigInteger.ZERO.equals(val);
}
if (val instanceof Number) {
double d = ((Number) val).doubleValue();
return (!Double.isNaN(d) && d != 0.0);
}
if (val instanceof Scriptable) {
if (val instanceof ScriptableObject
&& ((ScriptableObject) val).avoidObjectDetection()) {
return false;
}
if (Context.getContext().isVersionECMA1()) {
// pure ECMA
return true;
}
// ECMA extension
val = ((Scriptable) val).getDefaultValue(BooleanClass);
if ((val instanceof Scriptable) && !isSymbol(val))
throw errorWithClassName("msg.primitive.expected", val);
continue;
}
warnAboutNonJSObject(val);
return true;
}
}
/**
* Convert the value to a number.
*
*
See ECMA 9.3.
*/
public static double toNumber(Object val) {
for (; ; ) {
if (val instanceof BigInteger) {
throw typeErrorById("msg.cant.convert.to.number", "BigInt");
}
if (val instanceof Number) return ((Number) val).doubleValue();
if (val == null) return +0.0;
if (Undefined.isUndefined(val)) return NaN;
if (val instanceof String) return toNumber((String) val);
if (val instanceof CharSequence) return toNumber(val.toString());
if (val instanceof Boolean) return ((Boolean) val).booleanValue() ? 1 : +0.0;
if (val instanceof Symbol) throw typeErrorById("msg.not.a.number");
if (val instanceof Scriptable) {
val = ((Scriptable) val).getDefaultValue(NumberClass);
if ((val instanceof Scriptable) && !isSymbol(val))
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;
}
public static final double NaN = Double.NaN;
public static final Double NaNobj = Double.valueOf(NaN);
// Preserve backward-compatibility with historical value of this.
public static final double negativeZero = Double.longBitsToDouble(0x8000000000000000L);
public static final Double zeroObj = Double.valueOf(0.0);
public static final Double negativeZeroObj = Double.valueOf(-0.0);
static double stringPrefixToNumber(String s, int start, int radix) {
return stringToNumber(s, start, s.length() - 1, radix, true);
}
static double stringToNumber(String s, int start, int end, int radix) {
return stringToNumber(s, start, end, radix, false);
}
/*
* Helper function for toNumber, parseInt, and TokenStream.getToken.
*/
private static double stringToNumber(
String source, int sourceStart, int sourceEnd, int radix, boolean isPrefix) {
char digitMax = '9';
char lowerCaseBound = 'a';
char upperCaseBound = 'A';
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 = sourceStart; end <= sourceEnd; end++) {
char c = source.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 if (!isPrefix) return NaN; // isn't a prefix but found unexpected char
else break; // unexpected char
sum = sum * radix + newDigit;
}
if (sourceStart == end) { // stopped right at the beginning
return NaN;
}
if (sum > NativeNumber.MAX_SAFE_INTEGER) {
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.parseDouble(source.substring(sourceStart, end));
} 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;
int pos = sourceStart;
for (; ; ) {
if (bitShiftInChar == 1) {
if (pos == end) break;
digit = source.charAt(pos++);
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 the #sec-tonumber-applied-to-the-string-type section of ECMA
*/
public static double toNumber(String s) {
final int len = s.length();
// Skip whitespace at the start
int start = 0;
char startChar;
for (; ; ) {
if (start == len) {
// empty or contains only whitespace
return +0.0;
}
startChar = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(startChar)) {
// found first non-whitespace character
break;
}
start++;
}
// Skip whitespace at the end
int end = len - 1;
char endChar;
while (ScriptRuntime.isStrWhiteSpaceChar(endChar = s.charAt(end))) {
end--;
}
// Do not break scripts relying on old non-compliant conversion
// (see bug #368)
// 1. makes ToNumber parse only a valid prefix in hex literals (similar to 'parseInt()')
// ToNumber('0x10 something') => 16
// 2. allows plus and minus signs for hexadecimal numbers
// ToNumber('-0x10') => -16
// 3. disables support for binary ('0b10') and octal ('0o13') literals
// ToNumber('0b1') => NaN
// ToNumber('0o5') => NaN
final Context cx = Context.getCurrentContext();
final boolean oldParsingMode = cx == null || cx.getLanguageVersion() < Context.VERSION_ES6;
// Handle non-base10 numbers
if (startChar == '0') {
if (start + 2 <= end) {
final char radixC = s.charAt(start + 1);
int radix = -1;
if (radixC == 'x' || radixC == 'X') {
radix = 16;
} else if (!oldParsingMode && (radixC == 'o' || radixC == 'O')) {
radix = 8;
} else if (!oldParsingMode && (radixC == 'b' || radixC == 'B')) {
radix = 2;
}
if (radix != -1) {
if (oldParsingMode) {
return stringPrefixToNumber(s, start + 2, radix);
}
return stringToNumber(s, start + 2, end, radix);
}
}
} else if (oldParsingMode && (startChar == '+' || startChar == '-')) {
// If in old parsing mode, check for a signed hexadecimal number
if (start + 3 <= end && s.charAt(start + 1) == '0') {
final char radixC = s.charAt(start + 2);
if (radixC == 'x' || radixC == 'X') {
double val = stringPrefixToNumber(s, start + 3, 16);
return startChar == '-' ? -val : val;
}
}
}
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 base10, non-infinity number:
// just try a normal floating point conversion
String sub = s.substring(start, end + 1);
// Quick test to check string contains only valid characters because
// Double.parseDouble() can be slow and accept input we want to reject
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.parseDouble(sub);
} catch (NumberFormatException ex) {
return NaN;
}
}
/** Convert the value to a BigInt. */
public static BigInteger toBigInt(Object val) {
for (; ; ) {
if (val instanceof BigInteger) {
return (BigInteger) val;
}
if (val instanceof BigDecimal) {
return ((BigDecimal) val).toBigInteger();
}
if (val instanceof Number) {
if (val instanceof Long) {
return BigInteger.valueOf(((Long) val));
} else {
double d = ((Number) val).doubleValue();
if (Double.isNaN(d) || Double.isInfinite(d)) {
throw rangeErrorById(
"msg.cant.convert.to.bigint.isnt.integer", toString(val));
}
BigDecimal bd = new BigDecimal(d, MathContext.UNLIMITED);
try {
return bd.toBigIntegerExact();
} catch (ArithmeticException e) {
throw rangeErrorById(
"msg.cant.convert.to.bigint.isnt.integer", toString(val));
}
}
}
if (val == null || Undefined.isUndefined(val)) {
throw typeErrorById("msg.cant.convert.to.bigint", toString(val));
}
if (val instanceof String) {
return toBigInt((String) val);
}
if (val instanceof CharSequence) {
return toBigInt(val.toString());
}
if (val instanceof Boolean) {
return ((Boolean) val).booleanValue() ? BigInteger.ONE : BigInteger.ZERO;
}
if (val instanceof Symbol) {
throw typeErrorById("msg.cant.convert.to.bigint", toString(val));
}
if (val instanceof Scriptable) {
val = ((Scriptable) val).getDefaultValue(BigIntegerClass);
if ((val instanceof Scriptable) && !isSymbol(val)) {
throw errorWithClassName("msg.primitive.expected", val);
}
continue;
}
warnAboutNonJSObject(val);
return BigInteger.ZERO;
}
}
/** ToBigInt applied to the String type */
public static BigInteger toBigInt(String s) {
final int len = s.length();
// Skip whitespace at the start
int start = 0;
char startChar;
for (; ; ) {
if (start == len) {
// empty or contains only whitespace
return BigInteger.ZERO;
}
startChar = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(startChar)) {
// found first non-whitespace character
break;
}
start++;
}
// Skip whitespace at the end
int end = len - 1;
while (ScriptRuntime.isStrWhiteSpaceChar(s.charAt(end))) {
end--;
}
// Handle non-base10 numbers
if (startChar == '0') {
if (start + 2 <= end) {
final char radixC = s.charAt(start + 1);
int radix = -1;
if (radixC == 'x' || radixC == 'X') {
radix = 16;
} else if (radixC == 'o' || radixC == 'O') {
radix = 8;
} else if (radixC == 'b' || radixC == 'B') {
radix = 2;
}
if (radix != -1) {
try {
return new BigInteger(s.substring(start + 2, end + 1), radix);
} catch (NumberFormatException ex) {
throw syntaxErrorById("msg.bigint.bad.form");
}
}
}
}
// A base10, non-infinity bigint:
// just try a normal biginteger conversion
String sub = s.substring(start, end + 1);
for (int i = sub.length() - 1; i >= 0; i--) {
char c = sub.charAt(i);
if (i == 0 && (c == '+' || c == '-')) {
continue;
}
if ('0' <= c && c <= '9') {
continue;
}
throw syntaxErrorById("msg.bigint.bad.form");
}
try {
return new BigInteger(sub);
} catch (NumberFormatException ex) {
throw syntaxErrorById("msg.bigint.bad.form");
}
}
/**
* Convert the value to a Numeric (Number or BigInt).
*
*
toNumber does not allow java.math.BigInteger. toNumeric allows java.math.BigInteger.
*
*
See ECMA 7.1.3 (v11.0).
*/
public static Number toNumeric(Object val) {
if (val instanceof Number) {
return (Number) val;
}
return toNumber(val);
}
public static int toIndex(Object val) {
if (Undefined.isUndefined(val)) {
return 0;
}
double integerIndex = toInteger(val);
if (integerIndex < 0) {
throw rangeError("index out of range");
}
// ToLength
double index = Math.min(integerIndex, NativeNumber.MAX_SAFE_INTEGER);
if (integerIndex != index) {
throw rangeError("index out of range");
}
return (int) index;
}
/**
* 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;
Object[] result = new Object[count];
System.arraycopy(args, 0, result, 0, args.length);
if (args.length < count) {
Arrays.fill(result, args.length, count, Undefined.instance);
}
return result;
}
/**
* 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. Also the rest parameter array construction is done here.
*/
public static Object[] padAndRestArguments(
Context cx, Scriptable scope, Object[] args, int argCount) {
Object[] result = new Object[argCount];
int paramCount = argCount - 1;
if (args.length < paramCount) {
System.arraycopy(args, 0, result, 0, args.length);
Arrays.fill(result, args.length, paramCount, Undefined.instance);
} else {
System.arraycopy(args, 0, result, 0, paramCount);
}
Object[] restValues;
if (args.length > paramCount) {
restValues = new Object[args.length - paramCount];
System.arraycopy(args, paramCount, restValues, 0, restValues.length);
} else {
restValues = ScriptRuntime.emptyArgs;
}
result[paramCount] = cx.newArray(scope, restValues);
return result;
}
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();
StringBuilder 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 StringBuilder(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, Context cx, boolean isStrict) {
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, cx.getLanguageVersion(), isStrict);
}
public static CharSequence toCharSequence(Object val) {
if (val instanceof NativeString) {
return ((NativeString) val).toCharSequence();
}
return val instanceof CharSequence ? (CharSequence) val : toString(val);
}
/**
* Convert the value to a string.
*
*
See ECMA 9.8.
*/
public static String toString(Object val) {
for (; ; ) {
if (val == null) {
return "null";
}
if (Undefined.isUndefined(val)) {
return "undefined";
}
if (val instanceof String) {
return (String) val;
}
if (val instanceof CharSequence) {
return val.toString();
}
if (val instanceof BigInteger) {
return val.toString();
}
if (val instanceof Number) {
// XXX should we just teach NativeNumber.stringValue()
// about Numbers?
return numberToString(((Number) val).doubleValue(), 10);
}
if (val instanceof Symbol) {
throw typeErrorById("msg.not.a.string");
}
if (val instanceof Scriptable) {
val = ((Scriptable) val).getDefaultValue(StringClass);
if ((val instanceof Scriptable) && !isSymbol(val)) {
throw errorWithClassName("msg.primitive.expected", val);
}
continue;
}
return val.toString();
}
}
static String defaultObjectToString(Scriptable obj) {
if (obj == null) return "[object Null]";
if (Undefined.isUndefined(obj)) return "[object Undefined]";
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 ((base < 2) || (base > 36)) {
throw Context.reportRuntimeErrorById("msg.bad.radix", Integer.toString(base));
}
if (Double.isNaN(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 != 10) {
return DToA.JS_dtobasestr(base, d);
}
// V8 FastDtoa can't convert all numbers, so try it first but
// fall back to old DToA in case it fails
String result = FastDtoa.numberToString(d);
if (result != null) {
return result;
}
StringBuilder buffer = new StringBuilder();
DToA.JS_dtostr(buffer, DToA.DTOSTR_STANDARD, 0, d);
return buffer.toString();
}
public static String bigIntToString(BigInteger n, int base) {
if ((base < 2) || (base > 36)) {
throw rangeErrorById("msg.bad.radix", Integer.toString(base));
}
return n.toString(base);
}
static String uneval(Context cx, Scriptable scope, Object value) {
if (value == null) {
return "null";
}
if (Undefined.isUndefined(value)) {
return "undefined";
}
if (value instanceof CharSequence) {
String escaped = escapeString(value.toString());
StringBuilder sb = new StringBuilder(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;
// Wrapped Java objects won't have "toSource" and will report
// errors for get()s of nonexistent name, so use has() first
if (ScriptableObject.hasProperty(obj, "toSource")) {
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);
}
StringBuilder result = new StringBuilder(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, cx, cx.isStrictMode())) {
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);
}
/**
* Warning: This doesn't allow to resolve primitive prototype properly when
* many top scopes are involved
*
* @deprecated Use {@link #toObjectOrNull(Context, Object, Scriptable)} instead
*/
@Deprecated
public static Scriptable toObjectOrNull(Context cx, Object obj) {
if (obj instanceof Scriptable) {
return (Scriptable) obj;
} else if (obj != null && !Undefined.isUndefined(obj)) {
return toObject(cx, getTopCallScope(cx), obj);
}
return null;
}
/** @param scope the scope that should be used to resolve primitive prototype */
public static Scriptable toObjectOrNull(Context cx, Object obj, Scriptable scope) {
if (obj instanceof Scriptable) {
return (Scriptable) obj;
} else if (obj != null && !Undefined.isUndefined(obj)) {
return toObject(cx, scope, obj);
}
return null;
}
/** @deprecated Use {@link #toObject(Scriptable, Object)} instead. */
@Deprecated
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 == null) {
throw typeErrorById("msg.null.to.object");
}
if (Undefined.isUndefined(val)) {
throw typeErrorById("msg.undef.to.object");
}
if (isSymbol(val)) {
if (val instanceof SymbolKey) {
NativeSymbol result = new NativeSymbol((SymbolKey) val);
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Symbol);
return result;
}
NativeSymbol result = new NativeSymbol((NativeSymbol) val);
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Symbol);
return result;
}
if (val instanceof Scriptable) {
return (Scriptable) val;
}
if (val instanceof CharSequence) {
// FIXME we want to avoid toString() here, especially for concat()
NativeString result = new NativeString((CharSequence) val);
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.String);
return result;
}
if (cx.getLanguageVersion() >= Context.VERSION_ES6 && val instanceof BigInteger) {
NativeBigInt result = new NativeBigInt(((BigInteger) val));
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.BigInt);
return result;
}
if (val instanceof Number) {
NativeNumber result = new NativeNumber(((Number) val).doubleValue());
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Number);
return result;
}
if (val instanceof Boolean) {
NativeBoolean result = new NativeBoolean(((Boolean) val).booleanValue());
setBuiltinProtoAndParent(result, scope, TopLevel.Builtins.Boolean);
return result;
}
// 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. */
@Deprecated
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. */
@Deprecated
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, scope);
if (thisObj == null) {
throw undefCallError(null, "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);
}
public static Scriptable newBuiltinObject(
Context cx, Scriptable scope, TopLevel.Builtins type, Object[] args) {
scope = ScriptableObject.getTopLevelScope(scope);
Function ctor = TopLevel.getBuiltinCtor(cx, scope, type);
if (args == null) {
args = ScriptRuntime.emptyArgs;
}
return ctor.construct(cx, scope, args);
}
static Scriptable newNativeError(
Context cx, Scriptable scope, TopLevel.NativeErrors type, Object[] args) {
scope = ScriptableObject.getTopLevelScope(scope);
Function ctor = TopLevel.getNativeErrorCtor(cx, scope, type);
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 (Double.isNaN(d)) return +0.0;
if ((d == 0.0) || Double.isInfinite(d)) return d;
if (d > 0.0) return Math.floor(d);
return Math.ceil(d);
}
public static double toInteger(Object[] args, int index) {
return (index < args.length) ? toInteger(args[index]) : +0.0;
}
public static long toLength(Object[] args, int index) {
double len = toInteger(args, index);
if (len <= 0.0) {
return 0;
}
return (long) Math.min(len, NativeNumber.MAX_SAFE_INTEGER);
}
/** 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) {
return DoubleConversion.doubleToInt32(d);
}
/**
* See ECMA 9.6.
*
* @return long value representing 32 bits unsigned integer
*/
public static long toUint32(double d) {
return DoubleConversion.doubleToInt32(d) & 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);
return (char) DoubleConversion.doubleToInt32(d);
}
/**
* If "arg" is a "canonical numeric index," which means any number constructed from a string
* that doesn't have extra whitespace or non-standard formatting, return it -- otherwise return
* an empty option. Defined in ECMA 7.1.21.
*/
public static Optional canonicalNumericIndexString(String arg) {
if ("-0".equals(arg)) {
return Optional.of(Double.NEGATIVE_INFINITY);
}
double num = toNumber(arg);
// According to tests, "NaN" is not a number ;-)
if (Double.isNaN(num)) {
return Optional.empty();
}
String numStr = toString(num);
if (numStr.equals(arg)) {
return Optional.of(num);
}
return Optional.empty();
}
// 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.reportRuntimeErrorById("msg.ctor.not.found", constructorName);
}
throw Context.reportRuntimeErrorById("msg.not.ctor", constructorName);
}
/**
* Return -1L if str is not an index, or the index value as lower 32 bits of the result. Note
* that the result needs to be cast to an int in order to produce the actual index, which may be
* negative.
*
* Note that this method on its own does not actually produce an index that is useful for an
* actual Object or Array, because it may be larger than Integer.MAX_VALUE. Most callers should
* instead call toStringOrIndex, which calls this under the covers.
*/
public 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);
if (c == '0') return -1L; // "-0" is not an index
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 && indexTest <= Integer.MAX_VALUE) {
return Integer.valueOf((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 Integer.valueOf(i);
}
return toString(d);
}
/**
* Helper to return a string or an integer. Always use a null check on s.stringId to determine
* if the result is string or integer.
*
* @see ScriptRuntime#toStringIdOrIndex(Context, Object)
*/
public static final class StringIdOrIndex {
final String stringId;
final int index;
StringIdOrIndex(String stringId) {
this.stringId = stringId;
this.index = -1;
}
StringIdOrIndex(int index) {
this.stringId = null;
this.index = index;
}
public String getStringId() {
return stringId;
}
public int getIndex() {
return index;
}
}
/**
* If id is a number or a string presentation of an int32 value, then id the returning
* StringIdOrIndex has the index set, otherwise the stringId is set.
*/
public static StringIdOrIndex toStringIdOrIndex(Object id) {
if (id instanceof Number) {
double d = ((Number) id).doubleValue();
if (d < 0.0) {
return new StringIdOrIndex(toString(id));
}
int index = (int) d;
if (index == d) {
return new StringIdOrIndex(index);
}
return new StringIdOrIndex(toString(id));
}
String s;
if (id instanceof String) {
s = (String) id;
} else {
s = toString(id);
}
long indexTest = indexFromString(s);
if (indexTest >= 0 && indexTest <= Integer.MAX_VALUE) {
return new StringIdOrIndex((int) indexTest);
}
return new StringIdOrIndex(s);
}
/**
* Call obj.[[Get]](id)
*
* @deprecated Use {@link #getObjectElem(Object, Object, Context, Scriptable)} instead
*/
@Deprecated
public static Object getObjectElem(Object obj, Object elem, Context cx) {
return getObjectElem(obj, elem, cx, getTopCallScope(cx));
}
/** Call obj.[[Get]](id) */
public static Object getObjectElem(Object obj, Object elem, Context cx, Scriptable scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
throw undefReadError(obj, elem);
}
return getObjectElem(sobj, elem, cx);
}
public static Object getObjectElem(Scriptable obj, Object elem, Context cx) {
Object result;
if (obj instanceof XMLObject) {
result = ((XMLObject) obj).get(cx, elem);
} else if (isSymbol(elem)) {
result = ScriptableObject.getProperty(obj, (Symbol) elem);
} else {
StringIdOrIndex s = toStringIdOrIndex(elem);
if (s.stringId == null) {
int index = s.index;
result = ScriptableObject.getProperty(obj, index);
} else {
result = ScriptableObject.getProperty(obj, s.stringId);
}
}
if (result == Scriptable.NOT_FOUND) {
result = Undefined.instance;
}
return result;
}
/**
* Version of getObjectElem when elem is a valid JS identifier name.
*
* @deprecated Use {@link #getObjectProp(Object, String, Context, Scriptable)} instead
*/
@Deprecated
public static Object getObjectProp(Object obj, String property, Context cx) {
return getObjectProp(obj, property, cx, getTopCallScope(cx));
}
/**
* Version of getObjectElem when elem is a valid JS identifier name.
*
* @param scope the scope that should be used to resolve primitive prototype
*/
public static Object getObjectProp(Object obj, String property, Context cx, Scriptable scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
throw undefReadError(obj, property);
}
return getObjectProp(sobj, property, cx);
}
public static Object getObjectProp(Scriptable obj, String property, Context cx) {
Object result = ScriptableObject.getProperty(obj, property);
if (result == Scriptable.NOT_FOUND) {
if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) {
Context.reportWarning(
ScriptRuntime.getMessageById("msg.ref.undefined.prop", property));
}
result = Undefined.instance;
}
return result;
}
/** @deprecated Use {@link #getObjectPropNoWarn(Object, String, Context, Scriptable)} instead */
@Deprecated
public static Object getObjectPropNoWarn(Object obj, String property, Context cx) {
return getObjectPropNoWarn(obj, property, cx, getTopCallScope(cx));
}
public static Object getObjectPropNoWarn(
Object obj, String property, Context cx, Scriptable scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
throw undefReadError(obj, property);
}
Object result = ScriptableObject.getProperty(sobj, property);
if (result == Scriptable.NOT_FOUND) {
return Undefined.instance;
}
return result;
}
/**
* A cheaper and less general version of the above for well-known argument types.
*
* @deprecated Use {@link #getObjectIndex(Object, double, Context, Scriptable)} instead
*/
@Deprecated
public static Object getObjectIndex(Object obj, double dblIndex, Context cx) {
return getObjectIndex(obj, dblIndex, cx, getTopCallScope(cx));
}
/** 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 scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
throw undefReadError(obj, toString(dblIndex));
}
int index = (int) dblIndex;
if (index == dblIndex && index >= 0) {
return getObjectIndex(sobj, index, cx);
}
String s = toString(dblIndex);
return getObjectProp(sobj, s, cx);
}
public static Object getObjectIndex(Scriptable obj, int index, Context cx) {
Object result = ScriptableObject.getProperty(obj, index);
if (result == Scriptable.NOT_FOUND) {
result = Undefined.instance;
}
return result;
}
/**
* Call obj.[[Put]](id, value)
*
* @deprecated Use {@link #setObjectElem(Object, Object, Object, Context, Scriptable)} instead
*/
@Deprecated
public static Object setObjectElem(Object obj, Object elem, Object value, Context cx) {
return setObjectElem(obj, elem, value, cx, getTopCallScope(cx));
}
/** Call obj.[[Put]](id, value) */
public static Object setObjectElem(
Object obj, Object elem, Object value, Context cx, Scriptable scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
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) obj).put(cx, elem, value);
} else if (isSymbol(elem)) {
ScriptableObject.putProperty(obj, (Symbol) elem, value);
} else {
StringIdOrIndex s = toStringIdOrIndex(elem);
if (s.stringId == null) {
ScriptableObject.putProperty(obj, s.index, value);
} else {
ScriptableObject.putProperty(obj, s.stringId, value);
}
}
return value;
}
/**
* Version of setObjectElem when elem is a valid JS identifier name.
*
* @deprecated Use {@link #setObjectProp(Object, String, Object, Context, Scriptable)} instead
*/
@Deprecated
public static Object setObjectProp(Object obj, String property, Object value, Context cx) {
return setObjectProp(obj, property, value, cx, getTopCallScope(cx));
}
/** Version of setObjectElem when elem is a valid JS identifier name. */
public static Object setObjectProp(
Object obj, String property, Object value, Context cx, Scriptable scope) {
if (!(obj instanceof Scriptable)
&& cx.isStrictMode()
&& cx.getLanguageVersion() >= Context.VERSION_1_8) {
throw undefWriteError(obj, property, value);
}
Scriptable sobj = toObjectOrNull(cx, obj, scope);
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) {
ScriptableObject.putProperty(obj, property, value);
return value;
}
/**
* A cheaper and less general version of the above for well-known argument types.
*
* @deprecated Use {@link #setObjectIndex(Object, double, Object, Context, Scriptable)} instead
*/
@Deprecated
public static Object setObjectIndex(Object obj, double dblIndex, Object value, Context cx) {
return setObjectIndex(obj, dblIndex, value, cx, getTopCallScope(cx));
}
/** 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 scope) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
throw undefWriteError(obj, String.valueOf(dblIndex), value);
}
int index = (int) dblIndex;
if (index == dblIndex && index >= 0) {
return setObjectIndex(sobj, index, value, cx);
}
String s = toString(dblIndex);
return setObjectProp(sobj, s, value, cx);
}
public static Object setObjectIndex(Scriptable obj, int index, Object value, Context cx) {
ScriptableObject.putProperty(obj, index, value);
return value;
}
public static boolean deleteObjectElem(Scriptable target, Object elem, Context cx) {
if (isSymbol(elem)) {
SymbolScriptable so = ScriptableObject.ensureSymbolScriptable(target);
Symbol s = (Symbol) elem;
so.delete(s);
return !so.has(s, target);
}
StringIdOrIndex s = toStringIdOrIndex(elem);
if (s.stringId == null) {
target.delete(s.index);
return !target.has(s.index, target);
}
target.delete(s.stringId);
return !target.has(s.stringId, target);
}
public static boolean hasObjectElem(Scriptable target, Object elem, Context cx) {
boolean result;
if (isSymbol(elem)) {
result = ScriptableObject.hasProperty(target, (Symbol) elem);
} else {
StringIdOrIndex s = toStringIdOrIndex(elem);
if (s.stringId == null) {
result = ScriptableObject.hasProperty(target, s.index);
} else {
result = ScriptableObject.hasProperty(target, s.stringId);
}
}
return result;
}
public static Object refGet(Ref ref, Context cx) {
return ref.get(cx);
}
/** @deprecated Use {@link #refSet(Ref, Object, Context, Scriptable)} instead */
@Deprecated
public static Object refSet(Ref ref, Object value, Context cx) {
return refSet(ref, value, cx, getTopCallScope(cx));
}
public static Object refSet(Ref ref, Object value, Context cx, Scriptable scope) {
return ref.set(cx, scope, 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__");
}
/** @deprecated Use {@link #specialRef(Object, String, Context, Scriptable)} instead */
@Deprecated
public static Ref specialRef(Object obj, String specialProperty, Context cx) {
return specialRef(obj, specialProperty, cx, getTopCallScope(cx));
}
public static Ref specialRef(Object obj, String specialProperty, Context cx, Scriptable scope) {
return SpecialRef.createSpecial(cx, scope, obj, specialProperty);
}
/** @deprecated Use {@link #delete(Object, Object, Context, Scriptable, boolean)} instead */
@Deprecated
public static Object delete(Object obj, Object id, Context cx) {
return delete(obj, id, cx, false);
}
/**
* 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.
*
* @deprecated Use {@link #delete(Object, Object, Context, Scriptable, boolean)} instead
*/
@Deprecated
public static Object delete(Object obj, Object id, Context cx, boolean isName) {
return delete(obj, id, cx, getTopCallScope(cx), isName);
}
/**
* 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 scope, boolean isName) {
Scriptable sobj = toObjectOrNull(cx, obj, scope);
if (sobj == null) {
if (isName) {
return Boolean.TRUE;
}
throw undefDeleteError(obj, id);
}
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.has(name, xmlObj)) {
// function this should be the target object of with
thisObj = xmlObj;
result = xmlObj.get(name, xmlObj);
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.get(name, firstXMLObject);
}
// 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.has(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) {
// TODO: we used to special-case XMLObject here, but putProperty
// seems to work for E4X and it's better to optimize the common case
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.getMessageById("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 strictSetName(
Scriptable bound, Object value, Context cx, Scriptable scope, String id) {
if (bound != null) {
// TODO: The LeftHandSide also may not be a reference to a
// data property with the attribute value {[[Writable]]:false},
// to an accessor property with the attribute value
// {[[Put]]:undefined}, nor to a non-existent property of an
// object whose [[Extensible]] internal property has the value
// false. In these cases a TypeError exception is thrown (11.13.1).
// TODO: we used to special-case XMLObject here, but putProperty
// seems to work for E4X and we should optimize the common case
ScriptableObject.putProperty(bound, id, value);
return value;
}
// See ES5 8.7.2
String msg = "Assignment to undefined \"" + id + "\" in strict mode";
throw constructError("ReferenceError", msg);
}
public static Object setConst(Scriptable bound, Object value, Context cx, String id) {
if (bound instanceof XMLObject) {
bound.put(id, bound, 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 implements Serializable {
private static final long serialVersionUID = 1L;
Scriptable obj;
Object[] ids;
ObjToIntMap used;
Object currentId;
int index;
int enumType; /* one of ENUM_INIT_KEYS, ENUM_INIT_VALUES,
ENUM_INIT_ARRAY, ENUMERATE_VALUES_IN_ORDER */
// if true, integer ids will be returned as numbers rather than strings
boolean enumNumbers;
Scriptable iterator;
}
public static Scriptable toIterator(
Context cx, Scriptable scope, Scriptable obj, boolean keyOnly) {
if (ScriptableObject.hasProperty(obj, NativeIterator.ITERATOR_PROPERTY_NAME)) {
Object v = ScriptableObject.getProperty(obj, NativeIterator.ITERATOR_PROPERTY_NAME);
if (!(v instanceof Callable)) {
throw typeErrorById("msg.invalid.iterator");
}
Callable f = (Callable) v;
Object[] args = new Object[] {keyOnly ? Boolean.TRUE : Boolean.FALSE};
v = f.call(cx, scope, obj, args);
if (!(v instanceof Scriptable)) {
throw typeErrorById("msg.iterator.primitive");
}
return (Scriptable) v;
}
return null;
}
/**
* For backwards compatibility with generated class files
*
* @deprecated Use {@link #enumInit(Object, Context, Scriptable, int)} instead
*/
@Deprecated
public static Object enumInit(Object value, Context cx, boolean enumValues) {
return enumInit(value, cx, enumValues ? ENUMERATE_VALUES : ENUMERATE_KEYS);
}
public static final int ENUMERATE_KEYS = 0;
public static final int ENUMERATE_VALUES = 1;
public static final int ENUMERATE_ARRAY = 2;
public static final int ENUMERATE_KEYS_NO_ITERATOR = 3;
public static final int ENUMERATE_VALUES_NO_ITERATOR = 4;
public static final int ENUMERATE_ARRAY_NO_ITERATOR = 5;
public static final int ENUMERATE_VALUES_IN_ORDER = 6;
/** @deprecated Use {@link #enumInit(Object, Context, Scriptable, int)} instead */
@Deprecated
public static Object enumInit(Object value, Context cx, int enumType) {
return enumInit(value, cx, getTopCallScope(cx), enumType);
}
public static Object enumInit(Object value, Context cx, Scriptable scope, int enumType) {
IdEnumeration x = new IdEnumeration();
x.obj = toObjectOrNull(cx, value, scope);
// "for of" loop
if (enumType == ENUMERATE_VALUES_IN_ORDER) {
x.enumType = enumType;
x.iterator = null;
return enumInitInOrder(cx, x);
}
if (x.obj == null) {
// null or undefined do not cause errors but rather lead to empty
// "for in" loop
return x;
}
x.enumType = enumType;
x.iterator = null;
if (enumType != ENUMERATE_KEYS_NO_ITERATOR
&& enumType != ENUMERATE_VALUES_NO_ITERATOR
&& enumType != ENUMERATE_ARRAY_NO_ITERATOR) {
x.iterator =
toIterator(
cx,
x.obj.getParentScope(),
x.obj,
enumType == ScriptRuntime.ENUMERATE_KEYS);
}
if (x.iterator == null) {
// 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;
}
private static Object enumInitInOrder(Context cx, IdEnumeration x) {
if (!(x.obj instanceof SymbolScriptable)
|| !ScriptableObject.hasProperty(x.obj, SymbolKey.ITERATOR)) {
throw typeErrorById("msg.not.iterable", toString(x.obj));
}
Object iterator = ScriptableObject.getProperty(x.obj, SymbolKey.ITERATOR);
if (!(iterator instanceof Callable)) {
throw typeErrorById("msg.not.iterable", toString(x.obj));
}
Callable f = (Callable) iterator;
Scriptable scope = x.obj.getParentScope();
Object[] args = new Object[] {};
Object v = f.call(cx, scope, x.obj, args);
if (!(v instanceof Scriptable)) {
throw typeErrorById("msg.not.iterable", toString(x.obj));
}
x.iterator = (Scriptable) v;
return x;
}
public static void setEnumNumbers(Object enumObj, boolean enumNumbers) {
((IdEnumeration) enumObj).enumNumbers = enumNumbers;
}
/** @deprecated since 1.7.15. Use {@link #enumNext(Context, Object)} instead */
@Deprecated
public static Boolean enumNext(Object enumObj) {
return enumNext(enumObj, Context.getContext());
}
public static Boolean enumNext(Object enumObj, Context cx) {
IdEnumeration x = (IdEnumeration) enumObj;
if (x.iterator != null) {
if (x.enumType == ENUMERATE_VALUES_IN_ORDER) {
return enumNextInOrder(x, cx);
}
Object v = ScriptableObject.getProperty(x.iterator, "next");
if (!(v instanceof Callable)) return Boolean.FALSE;
Callable f = (Callable) v;
try {
x.currentId = f.call(cx, x.iterator.getParentScope(), x.iterator, emptyArgs);
return Boolean.TRUE;
} catch (JavaScriptException e) {
if (e.getValue() instanceof NativeIterator.StopIteration) {
return Boolean.FALSE;
}
throw e;
}
}
for (; ; ) {
if (x.obj == null) {
return Boolean.FALSE;
}
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 Symbol) {
continue;
} else 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 = x.enumNumbers ? Integer.valueOf(intId) : String.valueOf(intId);
}
return Boolean.TRUE;
}
}
private static Boolean enumNextInOrder(IdEnumeration enumObj, Context cx) {
Object v = ScriptableObject.getProperty(enumObj.iterator, ES6Iterator.NEXT_METHOD);
if (!(v instanceof Callable)) {
throw notFunctionError(enumObj.iterator, ES6Iterator.NEXT_METHOD);
}
Callable f = (Callable) v;
Scriptable scope = enumObj.iterator.getParentScope();
Object r = f.call(cx, scope, enumObj.iterator, emptyArgs);
Scriptable iteratorResult = toObject(cx, scope, r);
Object done = ScriptableObject.getProperty(iteratorResult, ES6Iterator.DONE_PROPERTY);
if (done != Scriptable.NOT_FOUND && toBoolean(done)) {
return Boolean.FALSE;
}
enumObj.currentId =
ScriptableObject.getProperty(iteratorResult, ES6Iterator.VALUE_PROPERTY);
return Boolean.TRUE;
}
public static Object enumId(Object enumObj, Context cx) {
IdEnumeration x = (IdEnumeration) enumObj;
if (x.iterator != null) {
return x.currentId;
}
switch (x.enumType) {
case ENUMERATE_KEYS:
case ENUMERATE_KEYS_NO_ITERATOR:
return x.currentId;
case ENUMERATE_VALUES:
case ENUMERATE_VALUES_NO_ITERATOR:
return enumValue(enumObj, cx);
case ENUMERATE_ARRAY:
case ENUMERATE_ARRAY_NO_ITERATOR:
Object[] elements = {x.currentId, enumValue(enumObj, cx)};
return cx.newArray(ScriptableObject.getTopLevelScope(x.obj), elements);
default:
throw Kit.codeBug();
}
}
public static Object enumValue(Object enumObj, Context cx) {
IdEnumeration x = (IdEnumeration) enumObj;
Object result;
if (isSymbol(x.currentId)) {
SymbolScriptable so = ScriptableObject.ensureSymbolScriptable(x.obj);
result = so.get((Symbol) x.currentId, x.obj);
} else {
StringIdOrIndex s = toStringIdOrIndex(x.currentId);
if (s.stringId == null) {
result = x.obj.get(s.index, x.obj);
} else {
result = x.obj.get(s.stringId, 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;
}
/**
* This is used to handle all the special cases that are required when invoking
* Object.fromEntries or constructing a NativeMap or NativeWeakMap from an iterable.
*
* @param cx the current context
* @param scope the current scope
* @param arg1 the iterable object.
* @param setter the setter to set the value
* @return true, if arg1 was iterable.
*/
public static boolean loadFromIterable(
Context cx, Scriptable scope, Object arg1, BiConsumer