org.mozilla.javascript.NativeGlobal 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 static org.mozilla.javascript.ScriptableObject.DONTENUM;
import static org.mozilla.javascript.ScriptableObject.PERMANENT;
import static org.mozilla.javascript.ScriptableObject.READONLY;
import java.io.Serializable;
import org.mozilla.javascript.xml.XMLLib;
/**
* This class implements the global native object (function and value properties only).
*
* See ECMA 15.1.[12].
*
* @author Mike Shaver
*/
public class NativeGlobal implements Serializable, IdFunctionCall {
static final long serialVersionUID = 6080442165748707530L;
public static void init(Context cx, Scriptable scope, boolean sealed) {
NativeGlobal obj = new NativeGlobal();
for (int id = 1; id <= LAST_SCOPE_FUNCTION_ID; ++id) {
String name;
int arity = 1;
switch (id) {
case Id_decodeURI:
name = "decodeURI";
break;
case Id_decodeURIComponent:
name = "decodeURIComponent";
break;
case Id_encodeURI:
name = "encodeURI";
break;
case Id_encodeURIComponent:
name = "encodeURIComponent";
break;
case Id_escape:
name = "escape";
break;
case Id_eval:
name = "eval";
break;
case Id_isFinite:
name = "isFinite";
break;
case Id_isNaN:
name = "isNaN";
break;
case Id_isXMLName:
name = "isXMLName";
break;
case Id_parseFloat:
name = "parseFloat";
break;
case Id_parseInt:
name = "parseInt";
arity = 2;
break;
case Id_unescape:
name = "unescape";
break;
case Id_uneval:
name = "uneval";
break;
default:
throw Kit.codeBug();
}
IdFunctionObject f = new IdFunctionObject(obj, FTAG, id, name, arity, scope);
if (sealed) {
f.sealObject();
}
f.exportAsScopeProperty();
}
ScriptableObject.defineProperty(
scope, "NaN", ScriptRuntime.NaNobj, READONLY | DONTENUM | PERMANENT);
ScriptableObject.defineProperty(
scope,
"Infinity",
ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY),
READONLY | DONTENUM | PERMANENT);
ScriptableObject.defineProperty(
scope, "undefined", Undefined.instance, READONLY | DONTENUM | PERMANENT);
ScriptableObject.defineProperty(scope, "globalThis", scope, DONTENUM);
/*
Each error constructor gets its own Error object as a prototype,
with the 'name' property set to the name of the error.
*/
Scriptable nativeError =
ScriptableObject.ensureScriptable(ScriptableObject.getProperty(scope, "Error"));
Scriptable nativeErrorProto =
ScriptableObject.ensureScriptable(
ScriptableObject.getProperty(nativeError, "prototype"));
for (TopLevel.NativeErrors error : TopLevel.NativeErrors.values()) {
if (error == TopLevel.NativeErrors.Error) {
// Error is initialized elsewhere and we should not overwrite it.
continue;
}
String name = error.name();
ScriptableObject errorProto =
(ScriptableObject)
ScriptRuntime.newBuiltinObject(
cx, scope, TopLevel.Builtins.Error, ScriptRuntime.emptyArgs);
errorProto.defineProperty("name", name, DONTENUM);
errorProto.defineProperty("message", "", DONTENUM);
IdFunctionObject ctor =
new IdFunctionObject(obj, FTAG, Id_new_CommonError, name, 1, scope);
ctor.markAsConstructor(errorProto);
ctor.setPrototype(nativeError);
errorProto.put("constructor", errorProto, ctor);
errorProto.setAttributes("constructor", ScriptableObject.DONTENUM);
errorProto.setPrototype(nativeErrorProto);
if (sealed) {
errorProto.sealObject();
ctor.sealObject();
}
ctor.setAttributes("name", DONTENUM | READONLY);
ctor.setAttributes("length", DONTENUM | READONLY);
ctor.exportAsScopeProperty();
}
}
@Override
public Object execIdCall(
IdFunctionObject f, Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
if (f.hasTag(FTAG)) {
int methodId = f.methodId();
switch (methodId) {
case Id_decodeURI:
case Id_decodeURIComponent:
{
String str = ScriptRuntime.toString(args, 0);
return decode(str, methodId == Id_decodeURI);
}
case Id_encodeURI:
case Id_encodeURIComponent:
{
String str = ScriptRuntime.toString(args, 0);
return encode(str, methodId == Id_encodeURI);
}
case Id_escape:
return js_escape(args);
case Id_eval:
return js_eval(cx, scope, args);
case Id_isFinite:
{
if (args.length < 1) {
return Boolean.FALSE;
}
return NativeNumber.isFinite(args[0]);
}
case Id_isNaN:
{
// The global method isNaN, as per ECMA-262 15.1.2.6.
boolean result;
if (args.length < 1) {
result = true;
} else {
double d = ScriptRuntime.toNumber(args[0]);
result = Double.isNaN(d);
}
return ScriptRuntime.wrapBoolean(result);
}
case Id_isXMLName:
{
Object name = (args.length == 0) ? Undefined.instance : args[0];
XMLLib xmlLib = XMLLib.extractFromScope(scope);
return ScriptRuntime.wrapBoolean(xmlLib.isXMLName(cx, name));
}
case Id_parseFloat:
return js_parseFloat(args);
case Id_parseInt:
return js_parseInt(args);
case Id_unescape:
return js_unescape(args);
case Id_uneval:
{
Object value = (args.length != 0) ? args[0] : Undefined.instance;
return ScriptRuntime.uneval(cx, scope, value);
}
case Id_new_CommonError:
// The implementation of all the ECMA error constructors
// (SyntaxError, TypeError, etc.)
return NativeError.make(cx, scope, f, args);
}
}
throw f.unknown();
}
/** The global method parseInt, as per ECMA-262 15.1.2.2. */
static Object js_parseInt(Object[] args) {
String s = ScriptRuntime.toString(args, 0);
int radix = ScriptRuntime.toInt32(args, 1);
int len = s.length();
if (len == 0) return ScriptRuntime.NaNobj;
boolean negative = false;
int start = 0;
char c;
do {
c = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(c)) break;
start++;
} while (start < len);
if (c == '+' || (negative = (c == '-'))) start++;
final int NO_RADIX = -1;
if (radix == 0) {
radix = NO_RADIX;
} else if (radix < 2 || radix > 36) {
return ScriptRuntime.NaNobj;
} else if (radix == 16 && len - start > 1 && s.charAt(start) == '0') {
c = s.charAt(start + 1);
if (c == 'x' || c == 'X') start += 2;
}
if (radix == NO_RADIX) {
radix = 10;
if (len - start > 1 && s.charAt(start) == '0') {
c = s.charAt(start + 1);
if (c == 'x' || c == 'X') {
radix = 16;
start += 2;
} else if ('0' <= c && c <= '9') {
Context cx = Context.getCurrentContext();
if (cx == null || cx.getLanguageVersion() < Context.VERSION_1_5) {
radix = 8;
start++;
}
}
}
}
double d = ScriptRuntime.stringPrefixToNumber(s, start, radix);
return ScriptRuntime.wrapNumber(negative ? -d : d);
}
/**
* The global method parseFloat, as per ECMA-262 15.1.2.3.
*
* @param args the arguments to parseFloat, ignoring args[>=1]
*/
static Object js_parseFloat(Object[] args) {
if (args.length < 1) return ScriptRuntime.NaNobj;
String s = ScriptRuntime.toString(args[0]);
int len = s.length();
int start = 0;
// Scan forward to skip whitespace
char c;
for (; ; ) {
if (start == len) {
return ScriptRuntime.NaNobj;
}
c = s.charAt(start);
if (!ScriptRuntime.isStrWhiteSpaceChar(c)) {
break;
}
++start;
}
int i = start;
if (c == '+' || c == '-') {
++i;
if (i == len) {
return ScriptRuntime.NaNobj;
}
c = s.charAt(i);
}
if (c == 'I') {
// check for "Infinity"
if (i + 8 <= len && s.regionMatches(i, "Infinity", 0, 8)) {
double d;
if (s.charAt(start) == '-') {
d = Double.NEGATIVE_INFINITY;
} else {
d = Double.POSITIVE_INFINITY;
}
return ScriptRuntime.wrapNumber(d);
}
return ScriptRuntime.NaNobj;
}
// Find the end of the legal bit
int decimal = -1;
int exponent = -1;
boolean exponentValid = false;
for (; i < len; i++) {
switch (s.charAt(i)) {
case '.':
if (decimal != -1) // Only allow a single decimal point.
break;
decimal = i;
continue;
case 'e':
case 'E':
if (exponent != -1) {
break;
} else if (i == len - 1) {
break;
}
exponent = i;
continue;
case '+':
case '-':
// Only allow '+' or '-' after 'e' or 'E'
if (exponent != i - 1) {
break;
} else if (i == len - 1) {
--i;
break;
}
continue;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (exponent != -1) {
exponentValid = true;
}
continue;
default:
break;
}
break;
}
if (exponent != -1 && !exponentValid) {
i = exponent;
}
s = s.substring(start, i);
try {
return Double.valueOf(s);
} catch (NumberFormatException ex) {
return ScriptRuntime.NaNobj;
}
}
/**
* The global method escape, as per ECMA-262 15.1.2.4.
*
*
Includes code for the 'mask' argument supported by the C escape method, which used to be
* part of the browser embedding. Blame for the strange constant names should be directed there.
*/
private static Object js_escape(Object[] args) {
final int URL_XALPHAS = 1, URL_XPALPHAS = 2, URL_PATH = 4;
String s = ScriptRuntime.toString(args, 0);
int mask = URL_XALPHAS | URL_XPALPHAS | URL_PATH;
if (args.length > 1) { // the 'mask' argument. Non-ECMA.
double d = ScriptRuntime.toNumber(args[1]);
if (Double.isNaN(d)
|| ((mask = (int) d) != d)
|| 0 != (mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))) {
throw Context.reportRuntimeErrorById("msg.bad.esc.mask");
}
}
StringBuilder sb = null;
for (int k = 0, L = s.length(); k != L; ++k) {
int c = s.charAt(k);
if (mask != 0
&& ((c >= '0' && c <= '9')
|| (c >= 'A' && c <= 'Z')
|| (c >= 'a' && c <= 'z')
|| c == '@'
|| c == '*'
|| c == '_'
|| c == '-'
|| c == '.'
|| (0 != (mask & URL_PATH) && (c == '/' || c == '+')))) {
if (sb != null) {
sb.append((char) c);
}
} else {
if (sb == null) {
sb = new StringBuilder(L + 3);
sb.append(s);
sb.setLength(k);
}
int hexSize;
if (c < 256) {
if (c == ' ' && mask == URL_XPALPHAS) {
sb.append('+');
continue;
}
sb.append('%');
hexSize = 2;
} else {
sb.append('%');
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();
}
/** The global unescape method, as per ECMA-262 15.1.2.5. */
private static Object js_unescape(Object[] args) {
String s = ScriptRuntime.toString(args, 0);
int firstEscapePos = s.indexOf('%');
if (firstEscapePos >= 0) {
int L = s.length();
char[] buf = s.toCharArray();
int destination = firstEscapePos;
for (int k = firstEscapePos; k != L; ) {
char c = buf[k];
++k;
if (c == '%' && k != L) {
int end, start;
if (buf[k] == 'u') {
start = k + 1;
end = k + 5;
} else {
start = k;
end = k + 2;
}
if (end <= L) {
int x = 0;
for (int i = start; i != end; ++i) {
x = Kit.xDigitToInt(buf[i], x);
}
if (x >= 0) {
c = (char) x;
k = end;
}
}
}
buf[destination] = c;
++destination;
}
s = new String(buf, 0, destination);
}
return s;
}
/**
* This is an indirect call to eval, and thus uses the global environment. Direct calls are
* executed via ScriptRuntime.callSpecial().
*/
private static Object js_eval(Context cx, Scriptable scope, Object[] args) {
Scriptable global = ScriptableObject.getTopLevelScope(scope);
return ScriptRuntime.evalSpecial(cx, global, global, args, "eval code", 1);
}
static boolean isEvalFunction(Object functionObj) {
if (functionObj instanceof IdFunctionObject) {
IdFunctionObject function = (IdFunctionObject) functionObj;
if (function.hasTag(FTAG) && function.methodId() == Id_eval) {
return true;
}
}
return false;
}
/** @deprecated Use {@link ScriptRuntime#constructError(String,String)} instead. */
@Deprecated
public static EcmaError constructError(
Context cx, String error, String message, Scriptable scope) {
return ScriptRuntime.constructError(error, message);
}
/**
* @deprecated Use {@link ScriptRuntime#constructError(String,String,String,int,String,int)}
* instead.
*/
@Deprecated
public static EcmaError constructError(
Context cx,
String error,
String message,
Scriptable scope,
String sourceName,
int lineNumber,
int columnNumber,
String lineSource) {
return ScriptRuntime.constructError(
error, message,
sourceName, lineNumber,
lineSource, columnNumber);
}
/*
* ECMA 3, 15.1.3 URI Handling Function Properties
*
* The following are implementations of the algorithms
* given in the ECMA specification for the hidden functions
* 'Encode' and 'Decode'.
*/
private static String encode(String str, boolean fullUri) {
byte[] utf8buf = null;
StringBuilder sb = null;
for (int k = 0, length = str.length(); k != length; ++k) {
char C = str.charAt(k);
if (encodeUnescaped(C, fullUri)) {
if (sb != null) {
sb.append(C);
}
} else {
if (sb == null) {
sb = new StringBuilder(length + 3);
sb.append(str);
sb.setLength(k);
utf8buf = new byte[6];
}
if (0xDC00 <= C && C <= 0xDFFF) {
throw uriError();
}
int V;
if (C < 0xD800 || 0xDBFF < C) {
V = C;
} else {
k++;
if (k == length) {
throw uriError();
}
char C2 = str.charAt(k);
if (!(0xDC00 <= C2 && C2 <= 0xDFFF)) {
throw uriError();
}
V = ((C - 0xD800) << 10) + (C2 - 0xDC00) + 0x10000;
}
int L = oneUcs4ToUtf8Char(utf8buf, V);
for (int j = 0; j < L; j++) {
int d = 0xff & utf8buf[j];
sb.append('%');
sb.append(toHexChar(d >>> 4));
sb.append(toHexChar(d & 0xf));
}
}
}
return (sb == null) ? str : sb.toString();
}
private static char toHexChar(int i) {
if (i >> 4 != 0) Kit.codeBug();
return (char) ((i < 10) ? i + '0' : i - 10 + 'A');
}
private static int unHex(char c) {
if ('A' <= c && c <= 'F') {
return c - 'A' + 10;
} else if ('a' <= c && c <= 'f') {
return c - 'a' + 10;
} else if ('0' <= c && c <= '9') {
return c - '0';
} else {
return -1;
}
}
private static int unHex(char c1, char c2) {
int i1 = unHex(c1);
int i2 = unHex(c2);
if (i1 >= 0 && i2 >= 0) {
return (i1 << 4) | i2;
}
return -1;
}
private static String decode(String str, boolean fullUri) {
char[] buf = null;
int bufTop = 0;
for (int k = 0, length = str.length(); k != length; ) {
char C = str.charAt(k);
if (C != '%') {
if (buf != null) {
buf[bufTop++] = C;
}
++k;
} else {
if (buf == null) {
// decode always compress so result can not be bigger then
// str.length()
buf = new char[length];
str.getChars(0, k, buf, 0);
bufTop = k;
}
int start = k;
if (k + 3 > length) throw uriError();
int B = unHex(str.charAt(k + 1), str.charAt(k + 2));
if (B < 0) throw uriError();
k += 3;
if ((B & 0x80) == 0) {
C = (char) B;
} else {
// Decode UTF-8 sequence into ucs4Char and encode it into
// UTF-16
int utf8Tail, ucs4Char, minUcs4Char;
if ((B & 0xC0) == 0x80) {
// First UTF-8 should be ouside 0x80..0xBF
throw uriError();
} else if ((B & 0x20) == 0) {
utf8Tail = 1;
ucs4Char = B & 0x1F;
minUcs4Char = 0x80;
} else if ((B & 0x10) == 0) {
utf8Tail = 2;
ucs4Char = B & 0x0F;
minUcs4Char = 0x800;
} else if ((B & 0x08) == 0) {
utf8Tail = 3;
ucs4Char = B & 0x07;
minUcs4Char = 0x10000;
} else if ((B & 0x04) == 0) {
utf8Tail = 4;
ucs4Char = B & 0x03;
minUcs4Char = 0x200000;
} else if ((B & 0x02) == 0) {
utf8Tail = 5;
ucs4Char = B & 0x01;
minUcs4Char = 0x4000000;
} else {
// First UTF-8 can not be 0xFF or 0xFE
throw uriError();
}
if (k + 3 * utf8Tail > length) throw uriError();
for (int j = 0; j != utf8Tail; j++) {
if (str.charAt(k) != '%') throw uriError();
B = unHex(str.charAt(k + 1), str.charAt(k + 2));
if (B < 0 || (B & 0xC0) != 0x80) throw uriError();
ucs4Char = (ucs4Char << 6) | (B & 0x3F);
k += 3;
}
// Check for overlongs and other should-not-present codes
if (ucs4Char < minUcs4Char || (ucs4Char >= 0xD800 && ucs4Char <= 0xDFFF)) {
ucs4Char = INVALID_UTF8;
} else if (ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) {
ucs4Char = 0xFFFD;
}
if (ucs4Char >= 0x10000) {
ucs4Char -= 0x10000;
if (ucs4Char > 0xFFFFF) {
throw uriError();
}
char H = (char) ((ucs4Char >>> 10) + 0xD800);
C = (char) ((ucs4Char & 0x3FF) + 0xDC00);
buf[bufTop++] = H;
} else {
C = (char) ucs4Char;
}
}
if (fullUri && URI_DECODE_RESERVED.indexOf(C) >= 0) {
for (int x = start; x != k; x++) {
buf[bufTop++] = str.charAt(x);
}
} else {
buf[bufTop++] = C;
}
}
}
return (buf == null) ? str : new String(buf, 0, bufTop);
}
private static boolean encodeUnescaped(char c, boolean fullUri) {
if (('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9')) {
return true;
}
if ("-_.!~*'()".indexOf(c) >= 0) {
return true;
}
if (fullUri) {
return URI_DECODE_RESERVED.indexOf(c) >= 0;
}
return false;
}
private static EcmaError uriError() {
return ScriptRuntime.constructError(
"URIError", ScriptRuntime.getMessageById("msg.bad.uri"));
}
private static final String URI_DECODE_RESERVED = ";/?:@&=+$,#";
private static final int INVALID_UTF8 = Integer.MAX_VALUE;
/* Convert one UCS-4 char and write it into a UTF-8 buffer, which must be
* at least 6 bytes long. Return the number of UTF-8 bytes of data written.
*/
private static int oneUcs4ToUtf8Char(byte[] utf8Buffer, int ucs4Char) {
int utf8Length = 1;
// JS_ASSERT(ucs4Char <= 0x7FFFFFFF);
if ((ucs4Char & ~0x7F) == 0) utf8Buffer[0] = (byte) ucs4Char;
else {
int i;
int a = ucs4Char >>> 11;
utf8Length = 2;
while (a != 0) {
a >>>= 5;
utf8Length++;
}
i = utf8Length;
while (--i > 0) {
utf8Buffer[i] = (byte) ((ucs4Char & 0x3F) | 0x80);
ucs4Char >>>= 6;
}
utf8Buffer[0] = (byte) (0x100 - (1 << (8 - utf8Length)) + ucs4Char);
}
return utf8Length;
}
private static final Object FTAG = "Global";
private static final int Id_decodeURI = 1,
Id_decodeURIComponent = 2,
Id_encodeURI = 3,
Id_encodeURIComponent = 4,
Id_escape = 5,
Id_eval = 6,
Id_isFinite = 7,
Id_isNaN = 8,
Id_isXMLName = 9,
Id_parseFloat = 10,
Id_parseInt = 11,
Id_unescape = 12,
Id_uneval = 13,
LAST_SCOPE_FUNCTION_ID = 13,
Id_new_CommonError = 14;
}