diff options
Diffstat (limited to 'trunk/trunk/infrastructure/rhino1_7R1/src/org')
119 files changed, 64440 insertions, 0 deletions
diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ByteCode.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ByteCode.java new file mode 100644 index 0000000..fa4713e --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ByteCode.java @@ -0,0 +1,274 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * 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.classfile; + +/** + * This class provides opcode values expected by the JVM in Java class files. + * + * It also provides tables for internal use by the ClassFileWriter. + * + * @author Roger Lawrence + */ +public class ByteCode { + + /** + * The byte opcodes defined by the Java Virtual Machine. + */ + public static final int + NOP = 0x00, + ACONST_NULL = 0x01, + ICONST_M1 = 0x02, + ICONST_0 = 0x03, + ICONST_1 = 0x04, + ICONST_2 = 0x05, + ICONST_3 = 0x06, + ICONST_4 = 0x07, + ICONST_5 = 0x08, + LCONST_0 = 0x09, + LCONST_1 = 0x0A, + FCONST_0 = 0x0B, + FCONST_1 = 0x0C, + FCONST_2 = 0x0D, + DCONST_0 = 0x0E, + DCONST_1 = 0x0F, + BIPUSH = 0x10, + SIPUSH = 0x11, + LDC = 0x12, + LDC_W = 0x13, + LDC2_W = 0x14, + ILOAD = 0x15, + LLOAD = 0x16, + FLOAD = 0x17, + DLOAD = 0x18, + ALOAD = 0x19, + ILOAD_0 = 0x1A, + ILOAD_1 = 0x1B, + ILOAD_2 = 0x1C, + ILOAD_3 = 0x1D, + LLOAD_0 = 0x1E, + LLOAD_1 = 0x1F, + LLOAD_2 = 0x20, + LLOAD_3 = 0x21, + FLOAD_0 = 0x22, + FLOAD_1 = 0x23, + FLOAD_2 = 0x24, + FLOAD_3 = 0x25, + DLOAD_0 = 0x26, + DLOAD_1 = 0x27, + DLOAD_2 = 0x28, + DLOAD_3 = 0x29, + ALOAD_0 = 0x2A, + ALOAD_1 = 0x2B, + ALOAD_2 = 0x2C, + ALOAD_3 = 0x2D, + IALOAD = 0x2E, + LALOAD = 0x2F, + FALOAD = 0x30, + DALOAD = 0x31, + AALOAD = 0x32, + BALOAD = 0x33, + CALOAD = 0x34, + SALOAD = 0x35, + ISTORE = 0x36, + LSTORE = 0x37, + FSTORE = 0x38, + DSTORE = 0x39, + ASTORE = 0x3A, + ISTORE_0 = 0x3B, + ISTORE_1 = 0x3C, + ISTORE_2 = 0x3D, + ISTORE_3 = 0x3E, + LSTORE_0 = 0x3F, + LSTORE_1 = 0x40, + LSTORE_2 = 0x41, + LSTORE_3 = 0x42, + FSTORE_0 = 0x43, + FSTORE_1 = 0x44, + FSTORE_2 = 0x45, + FSTORE_3 = 0x46, + DSTORE_0 = 0x47, + DSTORE_1 = 0x48, + DSTORE_2 = 0x49, + DSTORE_3 = 0x4A, + ASTORE_0 = 0x4B, + ASTORE_1 = 0x4C, + ASTORE_2 = 0x4D, + ASTORE_3 = 0x4E, + IASTORE = 0x4F, + LASTORE = 0x50, + FASTORE = 0x51, + DASTORE = 0x52, + AASTORE = 0x53, + BASTORE = 0x54, + CASTORE = 0x55, + SASTORE = 0x56, + POP = 0x57, + POP2 = 0x58, + DUP = 0x59, + DUP_X1 = 0x5A, + DUP_X2 = 0x5B, + DUP2 = 0x5C, + DUP2_X1 = 0x5D, + DUP2_X2 = 0x5E, + SWAP = 0x5F, + IADD = 0x60, + LADD = 0x61, + FADD = 0x62, + DADD = 0x63, + ISUB = 0x64, + LSUB = 0x65, + FSUB = 0x66, + DSUB = 0x67, + IMUL = 0x68, + LMUL = 0x69, + FMUL = 0x6A, + DMUL = 0x6B, + IDIV = 0x6C, + LDIV = 0x6D, + FDIV = 0x6E, + DDIV = 0x6F, + IREM = 0x70, + LREM = 0x71, + FREM = 0x72, + DREM = 0x73, + INEG = 0x74, + LNEG = 0x75, + FNEG = 0x76, + DNEG = 0x77, + ISHL = 0x78, + LSHL = 0x79, + ISHR = 0x7A, + LSHR = 0x7B, + IUSHR = 0x7C, + LUSHR = 0x7D, + IAND = 0x7E, + LAND = 0x7F, + IOR = 0x80, + LOR = 0x81, + IXOR = 0x82, + LXOR = 0x83, + IINC = 0x84, + I2L = 0x85, + I2F = 0x86, + I2D = 0x87, + L2I = 0x88, + L2F = 0x89, + L2D = 0x8A, + F2I = 0x8B, + F2L = 0x8C, + F2D = 0x8D, + D2I = 0x8E, + D2L = 0x8F, + D2F = 0x90, + I2B = 0x91, + I2C = 0x92, + I2S = 0x93, + LCMP = 0x94, + FCMPL = 0x95, + FCMPG = 0x96, + DCMPL = 0x97, + DCMPG = 0x98, + IFEQ = 0x99, + IFNE = 0x9A, + IFLT = 0x9B, + IFGE = 0x9C, + IFGT = 0x9D, + IFLE = 0x9E, + IF_ICMPEQ = 0x9F, + IF_ICMPNE = 0xA0, + IF_ICMPLT = 0xA1, + IF_ICMPGE = 0xA2, + IF_ICMPGT = 0xA3, + IF_ICMPLE = 0xA4, + IF_ACMPEQ = 0xA5, + IF_ACMPNE = 0xA6, + GOTO = 0xA7, + JSR = 0xA8, + RET = 0xA9, + TABLESWITCH = 0xAA, + LOOKUPSWITCH = 0xAB, + IRETURN = 0xAC, + LRETURN = 0xAD, + FRETURN = 0xAE, + DRETURN = 0xAF, + ARETURN = 0xB0, + RETURN = 0xB1, + GETSTATIC = 0xB2, + PUTSTATIC = 0xB3, + GETFIELD = 0xB4, + PUTFIELD = 0xB5, + INVOKEVIRTUAL = 0xB6, + INVOKESPECIAL = 0xB7, + INVOKESTATIC = 0xB8, + INVOKEINTERFACE = 0xB9, + NEW = 0xBB, + NEWARRAY = 0xBC, + ANEWARRAY = 0xBD, + ARRAYLENGTH = 0xBE, + ATHROW = 0xBF, + CHECKCAST = 0xC0, + INSTANCEOF = 0xC1, + MONITORENTER = 0xC2, + MONITOREXIT = 0xC3, + WIDE = 0xC4, + MULTIANEWARRAY = 0xC5, + IFNULL = 0xC6, + IFNONNULL = 0xC7, + GOTO_W = 0xC8, + JSR_W = 0xC9, + BREAKPOINT = 0xCA, + + IMPDEP1 = 0xFE, + IMPDEP2 = 0xFF; + + /** + * Types for the NEWARRAY opcode. + */ + public static final byte + T_BOOLEAN = 4, + T_CHAR = 5, + T_FLOAT = 6, + T_DOUBLE = 7, + T_BYTE = 8, + T_SHORT = 9, + T_INT = 10, + T_LONG = 11; + + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ClassFileWriter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ClassFileWriter.java new file mode 100644 index 0000000..b9c6c96 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/classfile/ClassFileWriter.java @@ -0,0 +1,3038 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * 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.classfile; + +import org.mozilla.javascript.ObjToIntMap; +import org.mozilla.javascript.ObjArray; +import org.mozilla.javascript.UintMap; + +import java.io.*; + +/** + * ClassFileWriter + * + * A ClassFileWriter is used to write a Java class file. Methods are + * provided to create fields and methods, and within methods to write + * Java bytecodes. + * + * @author Roger Lawrence + */ +public class ClassFileWriter { + + /** + * Thrown for cases where the error in generating the class file is + * due to a program size constraints rather than a likely bug in the + * compiler. + */ + public static class ClassFileFormatException extends RuntimeException { + ClassFileFormatException(String message) { + super(message); + } + } + + /** + * Construct a ClassFileWriter for a class. + * + * @param className the name of the class to write, including + * full package qualification. + * @param superClassName the name of the superclass of the class + * to write, including full package qualification. + * @param sourceFileName the name of the source file to use for + * producing debug information, or null if debug information + * is not desired + */ + public ClassFileWriter(String className, String superClassName, + String sourceFileName) + { + generatedClassName = className; + itsConstantPool = new ConstantPool(this); + itsThisClassIndex = itsConstantPool.addClass(className); + itsSuperClassIndex = itsConstantPool.addClass(superClassName); + if (sourceFileName != null) + itsSourceFileNameIndex = itsConstantPool.addUtf8(sourceFileName); + itsFlags = ACC_PUBLIC; + } + + public final String getClassName() + { + return generatedClassName; + } + + /** + * Add an interface implemented by this class. + * + * This method may be called multiple times for classes that + * implement multiple interfaces. + * + * @param interfaceName a name of an interface implemented + * by the class being written, including full package + * qualification. + */ + public void addInterface(String interfaceName) { + short interfaceIndex = itsConstantPool.addClass(interfaceName); + itsInterfaces.add(new Short(interfaceIndex)); + } + + public static final short + ACC_PUBLIC = 0x0001, + ACC_PRIVATE = 0x0002, + ACC_PROTECTED = 0x0004, + ACC_STATIC = 0x0008, + ACC_FINAL = 0x0010, + ACC_SYNCHRONIZED = 0x0020, + ACC_VOLATILE = 0x0040, + ACC_TRANSIENT = 0x0080, + ACC_NATIVE = 0x0100, + ACC_ABSTRACT = 0x0400; + + /** + * Set the class's flags. + * + * Flags must be a set of the following flags, bitwise or'd + * together: + * ACC_PUBLIC + * ACC_PRIVATE + * ACC_PROTECTED + * ACC_FINAL + * ACC_ABSTRACT + * TODO: check that this is the appropriate set + * @param flags the set of class flags to set + */ + public void setFlags(short flags) { + itsFlags = flags; + } + + static String getSlashedForm(String name) + { + return name.replace('.', '/'); + } + + /** + * Convert Java class name in dot notation into + * "Lname-with-dots-replaced-by-slashes;" form suitable for use as + * JVM type signatures. + */ + public static String classNameToSignature(String name) + { + int nameLength = name.length(); + int colonPos = 1 + nameLength; + char[] buf = new char[colonPos + 1]; + buf[0] = 'L'; + buf[colonPos] = ';'; + name.getChars(0, nameLength, buf, 1); + for (int i = 1; i != colonPos; ++i) { + if (buf[i] == '.') { + buf[i] = '/'; + } + } + return new String(buf, 0, colonPos + 1); + } + + /** + * Add a field to the class. + * + * @param fieldName the name of the field + * @param type the type of the field using ... + * @param flags the attributes of the field, such as ACC_PUBLIC, etc. + * bitwise or'd together + */ + public void addField(String fieldName, String type, short flags) { + short fieldNameIndex = itsConstantPool.addUtf8(fieldName); + short typeIndex = itsConstantPool.addUtf8(type); + itsFields.add(new ClassFileField(fieldNameIndex, typeIndex, flags)); + } + + /** + * Add a field to the class. + * + * @param fieldName the name of the field + * @param type the type of the field using ... + * @param flags the attributes of the field, such as ACC_PUBLIC, etc. + * bitwise or'd together + * @param value an initial integral value + */ + public void addField(String fieldName, String type, short flags, + int value) + { + short fieldNameIndex = itsConstantPool.addUtf8(fieldName); + short typeIndex = itsConstantPool.addUtf8(type); + ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex, + flags); + field.setAttributes(itsConstantPool.addUtf8("ConstantValue"), + (short)0, + (short)0, + itsConstantPool.addConstant(value)); + itsFields.add(field); + } + + /** + * Add a field to the class. + * + * @param fieldName the name of the field + * @param type the type of the field using ... + * @param flags the attributes of the field, such as ACC_PUBLIC, etc. + * bitwise or'd together + * @param value an initial long value + */ + public void addField(String fieldName, String type, short flags, + long value) + { + short fieldNameIndex = itsConstantPool.addUtf8(fieldName); + short typeIndex = itsConstantPool.addUtf8(type); + ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex, + flags); + field.setAttributes(itsConstantPool.addUtf8("ConstantValue"), + (short)0, + (short)2, + itsConstantPool.addConstant(value)); + itsFields.add(field); + } + + /** + * Add a field to the class. + * + * @param fieldName the name of the field + * @param type the type of the field using ... + * @param flags the attributes of the field, such as ACC_PUBLIC, etc. + * bitwise or'd together + * @param value an initial double value + */ + public void addField(String fieldName, String type, short flags, + double value) + { + short fieldNameIndex = itsConstantPool.addUtf8(fieldName); + short typeIndex = itsConstantPool.addUtf8(type); + ClassFileField field = new ClassFileField(fieldNameIndex, typeIndex, + flags); + field.setAttributes(itsConstantPool.addUtf8("ConstantValue"), + (short)0, + (short)2, + itsConstantPool.addConstant(value)); + itsFields.add(field); + } + + /** + * Add Information about java variable to use when generating the local + * variable table. + * + * @param name variable name. + * @param type variable type as bytecode descriptor string. + * @param startPC the starting bytecode PC where this variable is live, + * or -1 if it does not have a Java register. + * @param register the Java register number of variable + * or -1 if it does not have a Java register. + */ + public void addVariableDescriptor(String name, String type, int startPC, int register) + { + int nameIndex = itsConstantPool.addUtf8(name); + int descriptorIndex = itsConstantPool.addUtf8(type); + int [] chunk = { nameIndex, descriptorIndex, startPC, register }; + if (itsVarDescriptors == null) { + itsVarDescriptors = new ObjArray(); + } + itsVarDescriptors.add(chunk); + } + + /** + * Add a method and begin adding code. + * + * This method must be called before other methods for adding code, + * exception tables, etc. can be invoked. + * + * @param methodName the name of the method + * @param type a string representing the type + * @param flags the attributes of the field, such as ACC_PUBLIC, etc. + * bitwise or'd together + */ + public void startMethod(String methodName, String type, short flags) { + short methodNameIndex = itsConstantPool.addUtf8(methodName); + short typeIndex = itsConstantPool.addUtf8(type); + itsCurrentMethod = new ClassFileMethod(methodNameIndex, typeIndex, + flags); + itsMethods.add(itsCurrentMethod); + } + + /** + * Complete generation of the method. + * + * After this method is called, no more code can be added to the + * method begun with <code>startMethod</code>. + * + * @param maxLocals the maximum number of local variable slots + * (a.k.a. Java registers) used by the method + */ + public void stopMethod(short maxLocals) { + if (itsCurrentMethod == null) + throw new IllegalStateException("No method to stop"); + + fixLabelGotos(); + + itsMaxLocals = maxLocals; + + int lineNumberTableLength = 0; + if (itsLineNumberTable != null) { + // 6 bytes for the attribute header + // 2 bytes for the line number count + // 4 bytes for each entry + lineNumberTableLength = 6 + 2 + (itsLineNumberTableTop * 4); + } + + int variableTableLength = 0; + if (itsVarDescriptors != null) { + // 6 bytes for the attribute header + // 2 bytes for the variable count + // 10 bytes for each entry + variableTableLength = 6 + 2 + (itsVarDescriptors.size() * 10); + } + + int attrLength = 2 + // attribute_name_index + 4 + // attribute_length + 2 + // max_stack + 2 + // max_locals + 4 + // code_length + itsCodeBufferTop + + 2 + // exception_table_length + (itsExceptionTableTop * 8) + + 2 + // attributes_count + lineNumberTableLength + + variableTableLength; + + if (attrLength > 65536) { + // See http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html, + // section 4.10, "The amount of code per non-native, non-abstract + // method is limited to 65536 bytes... + throw new ClassFileFormatException( + "generated bytecode for method exceeds 64K limit."); + } + byte[] codeAttribute = new byte[attrLength]; + int index = 0; + int codeAttrIndex = itsConstantPool.addUtf8("Code"); + index = putInt16(codeAttrIndex, codeAttribute, index); + attrLength -= 6; // discount the attribute header + index = putInt32(attrLength, codeAttribute, index); + index = putInt16(itsMaxStack, codeAttribute, index); + index = putInt16(itsMaxLocals, codeAttribute, index); + index = putInt32(itsCodeBufferTop, codeAttribute, index); + System.arraycopy(itsCodeBuffer, 0, codeAttribute, index, + itsCodeBufferTop); + index += itsCodeBufferTop; + + if (itsExceptionTableTop > 0) { + index = putInt16(itsExceptionTableTop, codeAttribute, index); + for (int i = 0; i < itsExceptionTableTop; i++) { + ExceptionTableEntry ete = itsExceptionTable[i]; + short startPC = (short)getLabelPC(ete.itsStartLabel); + short endPC = (short)getLabelPC(ete.itsEndLabel); + short handlerPC = (short)getLabelPC(ete.itsHandlerLabel); + short catchType = ete.itsCatchType; + if (startPC == -1) + throw new IllegalStateException("start label not defined"); + if (endPC == -1) + throw new IllegalStateException("end label not defined"); + if (handlerPC == -1) + throw new IllegalStateException( + "handler label not defined"); + + index = putInt16(startPC, codeAttribute, index); + index = putInt16(endPC, codeAttribute, index); + index = putInt16(handlerPC, codeAttribute, index); + index = putInt16(catchType, codeAttribute, index); + } + } + else { + // write 0 as exception table length + index = putInt16(0, codeAttribute, index); + } + + int attributeCount = 0; + if (itsLineNumberTable != null) + attributeCount++; + if (itsVarDescriptors != null) + attributeCount++; + index = putInt16(attributeCount, codeAttribute, index); + + if (itsLineNumberTable != null) { + int lineNumberTableAttrIndex + = itsConstantPool.addUtf8("LineNumberTable"); + index = putInt16(lineNumberTableAttrIndex, codeAttribute, index); + int tableAttrLength = 2 + (itsLineNumberTableTop * 4); + index = putInt32(tableAttrLength, codeAttribute, index); + index = putInt16(itsLineNumberTableTop, codeAttribute, index); + for (int i = 0; i < itsLineNumberTableTop; i++) { + index = putInt32(itsLineNumberTable[i], codeAttribute, index); + } + } + + if (itsVarDescriptors != null) { + int variableTableAttrIndex + = itsConstantPool.addUtf8("LocalVariableTable"); + index = putInt16(variableTableAttrIndex, codeAttribute, index); + int varCount = itsVarDescriptors.size(); + int tableAttrLength = 2 + (varCount * 10); + index = putInt32(tableAttrLength, codeAttribute, index); + index = putInt16(varCount, codeAttribute, index); + for (int i = 0; i < varCount; i++) { + int[] chunk = (int[])itsVarDescriptors.get(i); + int nameIndex = chunk[0]; + int descriptorIndex = chunk[1]; + int startPC = chunk[2]; + int register = chunk[3]; + int length = itsCodeBufferTop - startPC; + + index = putInt16(startPC, codeAttribute, index); + index = putInt16(length, codeAttribute, index); + index = putInt16(nameIndex, codeAttribute, index); + index = putInt16(descriptorIndex, codeAttribute, index); + index = putInt16(register, codeAttribute, index); + } + } + + itsCurrentMethod.setCodeAttribute(codeAttribute); + + itsExceptionTable = null; + itsExceptionTableTop = 0; + itsLineNumberTableTop = 0; + itsCodeBufferTop = 0; + itsCurrentMethod = null; + itsMaxStack = 0; + itsStackTop = 0; + itsLabelTableTop = 0; + itsFixupTableTop = 0; + itsVarDescriptors = null; + } + + /** + * Add the single-byte opcode to the current method. + * + * @param theOpCode the opcode of the bytecode + */ + public void add(int theOpCode) { + if (opcodeCount(theOpCode) != 0) + throw new IllegalArgumentException("Unexpected operands"); + int newStack = itsStackTop + stackChange(theOpCode); + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + if (DEBUGCODE) + System.out.println("Add " + bytecodeStr(theOpCode)); + addToCodeBuffer(theOpCode); + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + } + + /** + * Add a single-operand opcode to the current method. + * + * @param theOpCode the opcode of the bytecode + * @param theOperand the operand of the bytecode + */ + public void add(int theOpCode, int theOperand) { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(theOpCode) + +", "+Integer.toHexString(theOperand)); + } + int newStack = itsStackTop + stackChange(theOpCode); + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + + switch (theOpCode) { + case ByteCode.GOTO : + // fallthru... + case ByteCode.IFEQ : + case ByteCode.IFNE : + case ByteCode.IFLT : + case ByteCode.IFGE : + case ByteCode.IFGT : + case ByteCode.IFLE : + case ByteCode.IF_ICMPEQ : + case ByteCode.IF_ICMPNE : + case ByteCode.IF_ICMPLT : + case ByteCode.IF_ICMPGE : + case ByteCode.IF_ICMPGT : + case ByteCode.IF_ICMPLE : + case ByteCode.IF_ACMPEQ : + case ByteCode.IF_ACMPNE : + case ByteCode.JSR : + case ByteCode.IFNULL : + case ByteCode.IFNONNULL : { + if ((theOperand & 0x80000000) != 0x80000000) { + if ((theOperand < 0) || (theOperand > 65535)) + throw new IllegalArgumentException( + "Bad label for branch"); + } + int branchPC = itsCodeBufferTop; + addToCodeBuffer(theOpCode); + if ((theOperand & 0x80000000) != 0x80000000) { + // hard displacement + addToCodeInt16(theOperand); + } + else { // a label + int targetPC = getLabelPC(theOperand); + if (DEBUGLABELS) { + int theLabel = theOperand & 0x7FFFFFFF; + System.out.println("Fixing branch to " + + theLabel + " at " + targetPC + + " from " + branchPC); + } + if (targetPC != -1) { + int offset = targetPC - branchPC; + addToCodeInt16(offset); + } + else { + addLabelFixup(theOperand, branchPC + 1); + addToCodeInt16(0); + } + } + } + break; + + case ByteCode.BIPUSH : + if ((byte)theOperand != theOperand) + throw new IllegalArgumentException("out of range byte"); + addToCodeBuffer(theOpCode); + addToCodeBuffer((byte)theOperand); + break; + + case ByteCode.SIPUSH : + if ((short)theOperand != theOperand) + throw new IllegalArgumentException("out of range short"); + addToCodeBuffer(theOpCode); + addToCodeInt16(theOperand); + break; + + case ByteCode.NEWARRAY : + if (!(0 <= theOperand && theOperand < 256)) + throw new IllegalArgumentException("out of range index"); + addToCodeBuffer(theOpCode); + addToCodeBuffer(theOperand); + break; + + case ByteCode.GETFIELD : + case ByteCode.PUTFIELD : + if (!(0 <= theOperand && theOperand < 65536)) + throw new IllegalArgumentException("out of range field"); + addToCodeBuffer(theOpCode); + addToCodeInt16(theOperand); + break; + + case ByteCode.LDC : + case ByteCode.LDC_W : + case ByteCode.LDC2_W : + if (!(0 <= theOperand && theOperand < 65536)) + throw new IllegalArgumentException("out of range index"); + if (theOperand >= 256 + || theOpCode == ByteCode.LDC_W + || theOpCode == ByteCode.LDC2_W) + { + if (theOpCode == ByteCode.LDC) { + addToCodeBuffer(ByteCode.LDC_W); + } else { + addToCodeBuffer(theOpCode); + } + addToCodeInt16(theOperand); + } else { + addToCodeBuffer(theOpCode); + addToCodeBuffer(theOperand); + } + break; + + case ByteCode.RET : + case ByteCode.ILOAD : + case ByteCode.LLOAD : + case ByteCode.FLOAD : + case ByteCode.DLOAD : + case ByteCode.ALOAD : + case ByteCode.ISTORE : + case ByteCode.LSTORE : + case ByteCode.FSTORE : + case ByteCode.DSTORE : + case ByteCode.ASTORE : + if (!(0 <= theOperand && theOperand < 65536)) + throw new ClassFileFormatException("out of range variable"); + if (theOperand >= 256) { + addToCodeBuffer(ByteCode.WIDE); + addToCodeBuffer(theOpCode); + addToCodeInt16(theOperand); + } + else { + addToCodeBuffer(theOpCode); + addToCodeBuffer(theOperand); + } + break; + + default : + throw new IllegalArgumentException( + "Unexpected opcode for 1 operand"); + } + + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + } + + /** + * Generate the load constant bytecode for the given integer. + * + * @param k the constant + */ + public void addLoadConstant(int k) { + switch (k) { + case 0: add(ByteCode.ICONST_0); break; + case 1: add(ByteCode.ICONST_1); break; + case 2: add(ByteCode.ICONST_2); break; + case 3: add(ByteCode.ICONST_3); break; + case 4: add(ByteCode.ICONST_4); break; + case 5: add(ByteCode.ICONST_5); break; + default: + add(ByteCode.LDC, itsConstantPool.addConstant(k)); + break; + } + } + + /** + * Generate the load constant bytecode for the given long. + * + * @param k the constant + */ + public void addLoadConstant(long k) { + add(ByteCode.LDC2_W, itsConstantPool.addConstant(k)); + } + + /** + * Generate the load constant bytecode for the given float. + * + * @param k the constant + */ + public void addLoadConstant(float k) { + add(ByteCode.LDC, itsConstantPool.addConstant(k)); + } + + /** + * Generate the load constant bytecode for the given double. + * + * @param k the constant + */ + public void addLoadConstant(double k) { + add(ByteCode.LDC2_W, itsConstantPool.addConstant(k)); + } + + /** + * Generate the load constant bytecode for the given string. + * + * @param k the constant + */ + public void addLoadConstant(String k) { + add(ByteCode.LDC, itsConstantPool.addConstant(k)); + } + + /** + * Add the given two-operand bytecode to the current method. + * + * @param theOpCode the opcode of the bytecode + * @param theOperand1 the first operand of the bytecode + * @param theOperand2 the second operand of the bytecode + */ + public void add(int theOpCode, int theOperand1, int theOperand2) { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(theOpCode) + +", "+Integer.toHexString(theOperand1) + +", "+Integer.toHexString(theOperand2)); + } + int newStack = itsStackTop + stackChange(theOpCode); + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + + if (theOpCode == ByteCode.IINC) { + if (!(0 <= theOperand1 && theOperand1 < 65536)) + throw new ClassFileFormatException("out of range variable"); + if (!(0 <= theOperand2 && theOperand2 < 65536)) + throw new ClassFileFormatException("out of range increment"); + + if (theOperand1 > 255 || theOperand2 < -128 || theOperand2 > 127) { + addToCodeBuffer(ByteCode.WIDE); + addToCodeBuffer(ByteCode.IINC); + addToCodeInt16(theOperand1); + addToCodeInt16(theOperand2); + } + else { + addToCodeBuffer(ByteCode.WIDE); + addToCodeBuffer(ByteCode.IINC); + addToCodeBuffer(theOperand1); + addToCodeBuffer(theOperand2); + } + } + else if (theOpCode == ByteCode.MULTIANEWARRAY) { + if (!(0 <= theOperand1 && theOperand1 < 65536)) + throw new IllegalArgumentException("out of range index"); + if (!(0 <= theOperand2 && theOperand2 < 256)) + throw new IllegalArgumentException("out of range dimensions"); + + addToCodeBuffer(ByteCode.MULTIANEWARRAY); + addToCodeInt16(theOperand1); + addToCodeBuffer(theOperand2); + } + else { + throw new IllegalArgumentException( + "Unexpected opcode for 2 operands"); + } + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + + } + + public void add(int theOpCode, String className) { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(theOpCode) + +", "+className); + } + int newStack = itsStackTop + stackChange(theOpCode); + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + switch (theOpCode) { + case ByteCode.NEW : + case ByteCode.ANEWARRAY : + case ByteCode.CHECKCAST : + case ByteCode.INSTANCEOF : { + short classIndex = itsConstantPool.addClass(className); + addToCodeBuffer(theOpCode); + addToCodeInt16(classIndex); + } + break; + + default : + throw new IllegalArgumentException( + "bad opcode for class reference"); + } + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + } + + + public void add(int theOpCode, String className, String fieldName, + String fieldType) + { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(theOpCode) + +", "+className+", "+fieldName+", "+fieldType); + } + int newStack = itsStackTop + stackChange(theOpCode); + char fieldTypeChar = fieldType.charAt(0); + int fieldSize = (fieldTypeChar == 'J' || fieldTypeChar == 'D') + ? 2 : 1; + switch (theOpCode) { + case ByteCode.GETFIELD : + case ByteCode.GETSTATIC : + newStack += fieldSize; + break; + case ByteCode.PUTSTATIC : + case ByteCode.PUTFIELD : + newStack -= fieldSize; + break; + default : + throw new IllegalArgumentException( + "bad opcode for field reference"); + } + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + short fieldRefIndex = itsConstantPool.addFieldRef(className, + fieldName, fieldType); + addToCodeBuffer(theOpCode); + addToCodeInt16(fieldRefIndex); + + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + } + + public void addInvoke(int theOpCode, String className, String methodName, + String methodType) + { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(theOpCode) + +", "+className+", "+methodName+", " + +methodType); + } + int parameterInfo = sizeOfParameters(methodType); + int parameterCount = parameterInfo >>> 16; + int stackDiff = (short)parameterInfo; + + int newStack = itsStackTop + stackDiff; + newStack += stackChange(theOpCode); // adjusts for 'this' + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + + switch (theOpCode) { + case ByteCode.INVOKEVIRTUAL : + case ByteCode.INVOKESPECIAL : + case ByteCode.INVOKESTATIC : + case ByteCode.INVOKEINTERFACE : { + addToCodeBuffer(theOpCode); + if (theOpCode == ByteCode.INVOKEINTERFACE) { + short ifMethodRefIndex + = itsConstantPool.addInterfaceMethodRef( + className, methodName, + methodType); + addToCodeInt16(ifMethodRefIndex); + addToCodeBuffer(parameterCount + 1); + addToCodeBuffer(0); + } + else { + short methodRefIndex = itsConstantPool.addMethodRef( + className, methodName, + methodType); + addToCodeInt16(methodRefIndex); + } + } + break; + + default : + throw new IllegalArgumentException( + "bad opcode for method reference"); + } + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(theOpCode) + +" stack = "+itsStackTop); + } + } + + /** + * Generate code to load the given integer on stack. + * + * @param k the constant + */ + public void addPush(int k) + { + if ((byte)k == k) { + if (k == -1) { + add(ByteCode.ICONST_M1); + } else if (0 <= k && k <= 5) { + add((byte)(ByteCode.ICONST_0 + k)); + } else { + add(ByteCode.BIPUSH, (byte)k); + } + } else if ((short)k == k) { + add(ByteCode.SIPUSH, (short)k); + } else { + addLoadConstant(k); + } + } + + public void addPush(boolean k) + { + add(k ? ByteCode.ICONST_1 : ByteCode.ICONST_0); + } + + /** + * Generate code to load the given long on stack. + * + * @param k the constant + */ + public void addPush(long k) + { + int ik = (int)k; + if (ik == k) { + addPush(ik); + add(ByteCode.I2L); + } else { + addLoadConstant(k); + } + } + + /** + * Generate code to load the given double on stack. + * + * @param k the constant + */ + public void addPush(double k) + { + if (k == 0.0) { + // zero + add(ByteCode.DCONST_0); + if (1.0 / k < 0) { + // Negative zero + add(ByteCode.DNEG); + } + } else if (k == 1.0 || k == -1.0) { + add(ByteCode.DCONST_1); + if (k < 0) { + add(ByteCode.DNEG); + } + } else { + addLoadConstant(k); + } + } + + /** + * Generate the code to leave on stack the given string even if the + * string encoding exeeds the class file limit for single string constant + * + * @param k the constant + */ + public void addPush(String k) { + int length = k.length(); + int limit = itsConstantPool.getUtfEncodingLimit(k, 0, length); + if (limit == length) { + addLoadConstant(k); + return; + } + // Split string into picies fitting the UTF limit and generate code for + // StringBuffer sb = new StringBuffer(length); + // sb.append(loadConstant(piece_1)); + // ... + // sb.append(loadConstant(piece_N)); + // sb.toString(); + final String SB = "java/lang/StringBuffer"; + add(ByteCode.NEW, SB); + add(ByteCode.DUP); + addPush(length); + addInvoke(ByteCode.INVOKESPECIAL, SB, "<init>", "(I)V"); + int cursor = 0; + for (;;) { + add(ByteCode.DUP); + String s = k.substring(cursor, limit); + addLoadConstant(s); + addInvoke(ByteCode.INVOKEVIRTUAL, SB, "append", + "(Ljava/lang/String;)Ljava/lang/StringBuffer;"); + add(ByteCode.POP); + if (limit == length) { + break; + } + cursor = limit; + limit = itsConstantPool.getUtfEncodingLimit(k, limit, length); + } + addInvoke(ByteCode.INVOKEVIRTUAL, SB, "toString", + "()Ljava/lang/String;"); + } + + /** + * Check if k fits limit on string constant size imposed by class file + * format. + * + * @param k the string constant + */ + public boolean isUnderStringSizeLimit(String k) + { + return itsConstantPool.isUnderUtfEncodingLimit(k); + } + + /** + * Store integer from stack top into the given local. + * + * @param local number of local register + */ + public void addIStore(int local) + { + xop(ByteCode.ISTORE_0, ByteCode.ISTORE, local); + } + + /** + * Store long from stack top into the given local. + * + * @param local number of local register + */ + public void addLStore(int local) + { + xop(ByteCode.LSTORE_0, ByteCode.LSTORE, local); + } + + /** + * Store float from stack top into the given local. + * + * @param local number of local register + */ + public void addFStore(int local) + { + xop(ByteCode.FSTORE_0, ByteCode.FSTORE, local); + } + + /** + * Store double from stack top into the given local. + * + * @param local number of local register + */ + public void addDStore(int local) + { + xop(ByteCode.DSTORE_0, ByteCode.DSTORE, local); + } + + /** + * Store object from stack top into the given local. + * + * @param local number of local register + */ + public void addAStore(int local) + { + xop(ByteCode.ASTORE_0, ByteCode.ASTORE, local); + } + + /** + * Load integer from the given local into stack. + * + * @param local number of local register + */ + public void addILoad(int local) + { + xop(ByteCode.ILOAD_0, ByteCode.ILOAD, local); + } + + /** + * Load long from the given local into stack. + * + * @param local number of local register + */ + public void addLLoad(int local) + { + xop(ByteCode.LLOAD_0, ByteCode.LLOAD, local); + } + + /** + * Load float from the given local into stack. + * + * @param local number of local register + */ + public void addFLoad(int local) + { + xop(ByteCode.FLOAD_0, ByteCode.FLOAD, local); + } + + /** + * Load double from the given local into stack. + * + * @param local number of local register + */ + public void addDLoad(int local) + { + xop(ByteCode.DLOAD_0, ByteCode.DLOAD, local); + } + + /** + * Load object from the given local into stack. + * + * @param local number of local register + */ + public void addALoad(int local) + { + xop(ByteCode.ALOAD_0, ByteCode.ALOAD, local); + } + + /** + * Load "this" into stack. + */ + public void addLoadThis() + { + add(ByteCode.ALOAD_0); + } + + private void xop(int shortOp, int op, int local) + { + switch (local) { + case 0: + add(shortOp); + break; + case 1: + add(shortOp + 1); + break; + case 2: + add(shortOp + 2); + break; + case 3: + add(shortOp + 3); + break; + default: + add(op, local); + } + } + + public int addTableSwitch(int low, int high) + { + if (DEBUGCODE) { + System.out.println("Add "+bytecodeStr(ByteCode.TABLESWITCH) + +" "+low+" "+high); + } + if (low > high) + throw new ClassFileFormatException("Bad bounds: "+low+' '+ high); + + int newStack = itsStackTop + stackChange(ByteCode.TABLESWITCH); + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + + int entryCount = high - low + 1; + int padSize = 3 & ~itsCodeBufferTop; // == 3 - itsCodeBufferTop % 4 + + int N = addReservedCodeSpace(1 + padSize + 4 * (1 + 2 + entryCount)); + int switchStart = N; + itsCodeBuffer[N++] = (byte)ByteCode.TABLESWITCH; + while (padSize != 0) { + itsCodeBuffer[N++] = 0; + --padSize; + } + N += 4; // skip default offset + N = putInt32(low, itsCodeBuffer, N); + putInt32(high, itsCodeBuffer, N); + + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+bytecodeStr(ByteCode.TABLESWITCH) + +" stack = "+itsStackTop); + } + + return switchStart; + } + + public final void markTableSwitchDefault(int switchStart) + { + setTableSwitchJump(switchStart, -1, itsCodeBufferTop); + } + + public final void markTableSwitchCase(int switchStart, int caseIndex) + { + setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop); + } + + public final void markTableSwitchCase(int switchStart, int caseIndex, + int stackTop) + { + if (!(0 <= stackTop && stackTop <= itsMaxStack)) + throw new IllegalArgumentException("Bad stack index: "+stackTop); + itsStackTop = (short)stackTop; + setTableSwitchJump(switchStart, caseIndex, itsCodeBufferTop); + } + + public void setTableSwitchJump(int switchStart, int caseIndex, + int jumpTarget) + { + if (!(0 <= jumpTarget && jumpTarget <= itsCodeBufferTop)) + throw new IllegalArgumentException("Bad jump target: "+jumpTarget); + if (!(caseIndex >= -1)) + throw new IllegalArgumentException("Bad case index: "+caseIndex); + + int padSize = 3 & ~switchStart; // == 3 - switchStart % 4 + int caseOffset; + if (caseIndex < 0) { + // default label + caseOffset = switchStart + 1 + padSize; + } else { + caseOffset = switchStart + 1 + padSize + 4 * (3 + caseIndex); + } + if (!(0 <= switchStart + && switchStart <= itsCodeBufferTop - 4 * 4 - padSize - 1)) + { + throw new IllegalArgumentException( + switchStart+" is outside a possible range of tableswitch" + +" in already generated code"); + } + if ((0xFF & itsCodeBuffer[switchStart]) != ByteCode.TABLESWITCH) { + throw new IllegalArgumentException( + switchStart+" is not offset of tableswitch statement"); + } + if (!(0 <= caseOffset && caseOffset + 4 <= itsCodeBufferTop)) { + // caseIndex >= -1 does not guarantee that caseOffset >= 0 due + // to a possible overflow. + throw new ClassFileFormatException( + "Too big case index: "+caseIndex); + } + // ALERT: perhaps check against case bounds? + putInt32(jumpTarget - switchStart, itsCodeBuffer, caseOffset); + } + + public int acquireLabel() + { + int top = itsLabelTableTop; + if (itsLabelTable == null || top == itsLabelTable.length) { + if (itsLabelTable == null) { + itsLabelTable = new int[MIN_LABEL_TABLE_SIZE]; + }else { + int[] tmp = new int[itsLabelTable.length * 2]; + System.arraycopy(itsLabelTable, 0, tmp, 0, top); + itsLabelTable = tmp; + } + } + itsLabelTableTop = top + 1; + itsLabelTable[top] = -1; + return top | 0x80000000; + } + + public void markLabel(int label) + { + if (!(label < 0)) + throw new IllegalArgumentException("Bad label, no biscuit"); + + label &= 0x7FFFFFFF; + if (label > itsLabelTableTop) + throw new IllegalArgumentException("Bad label"); + + if (itsLabelTable[label] != -1) { + throw new IllegalStateException("Can only mark label once"); + } + + itsLabelTable[label] = itsCodeBufferTop; + } + + public void markLabel(int label, short stackTop) + { + markLabel(label); + itsStackTop = stackTop; + } + + public void markHandler(int theLabel) { + itsStackTop = 1; + markLabel(theLabel); + } + + private int getLabelPC(int label) + { + if (!(label < 0)) + throw new IllegalArgumentException("Bad label, no biscuit"); + label &= 0x7FFFFFFF; + if (!(label < itsLabelTableTop)) + throw new IllegalArgumentException("Bad label"); + return itsLabelTable[label]; + } + + private void addLabelFixup(int label, int fixupSite) + { + if (!(label < 0)) + throw new IllegalArgumentException("Bad label, no biscuit"); + label &= 0x7FFFFFFF; + if (!(label < itsLabelTableTop)) + throw new IllegalArgumentException("Bad label"); + int top = itsFixupTableTop; + if (itsFixupTable == null || top == itsFixupTable.length) { + if (itsFixupTable == null) { + itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE]; + }else { + long[] tmp = new long[itsFixupTable.length * 2]; + System.arraycopy(itsFixupTable, 0, tmp, 0, top); + itsFixupTable = tmp; + } + } + itsFixupTableTop = top + 1; + itsFixupTable[top] = ((long)label << 32) | fixupSite; + } + + private void fixLabelGotos() + { + byte[] codeBuffer = itsCodeBuffer; + for (int i = 0; i < itsFixupTableTop; i++) { + long fixup = itsFixupTable[i]; + int label = (int)(fixup >> 32); + int fixupSite = (int)fixup; + int pc = itsLabelTable[label]; + if (pc == -1) { + // Unlocated label + throw new RuntimeException(); + } + // -1 to get delta from instruction start + int offset = pc - (fixupSite - 1); + if ((short)offset != offset) { + throw new ClassFileFormatException + ("Program too complex: too big jump offset"); + } + codeBuffer[fixupSite] = (byte)(offset >> 8); + codeBuffer[fixupSite + 1] = (byte)offset; + } + itsFixupTableTop = 0; + } + + /** + * Get the current offset into the code of the current method. + * + * @return an integer representing the offset + */ + public int getCurrentCodeOffset() { + return itsCodeBufferTop; + } + + public short getStackTop() { + return itsStackTop; + } + + public void setStackTop(short n) { + itsStackTop = n; + } + + public void adjustStackTop(int delta) { + int newStack = itsStackTop + delta; + if (newStack < 0 || Short.MAX_VALUE < newStack) badStack(newStack); + itsStackTop = (short)newStack; + if (newStack > itsMaxStack) itsMaxStack = (short)newStack; + if (DEBUGSTACK) { + System.out.println("After "+"adjustStackTop("+delta+")" + +" stack = "+itsStackTop); + } + } + + private void addToCodeBuffer(int b) + { + int N = addReservedCodeSpace(1); + itsCodeBuffer[N] = (byte)b; + } + + private void addToCodeInt16(int value) + { + int N = addReservedCodeSpace(2); + putInt16(value, itsCodeBuffer, N); + } + + private int addReservedCodeSpace(int size) + { + if (itsCurrentMethod == null) + throw new IllegalArgumentException("No method to add to"); + int oldTop = itsCodeBufferTop; + int newTop = oldTop + size; + if (newTop > itsCodeBuffer.length) { + int newSize = itsCodeBuffer.length * 2; + if (newTop > newSize) { newSize = newTop; } + byte[] tmp = new byte[newSize]; + System.arraycopy(itsCodeBuffer, 0, tmp, 0, oldTop); + itsCodeBuffer = tmp; + } + itsCodeBufferTop = newTop; + return oldTop; + } + + public void addExceptionHandler(int startLabel, int endLabel, + int handlerLabel, String catchClassName) + { + if ((startLabel & 0x80000000) != 0x80000000) + throw new IllegalArgumentException("Bad startLabel"); + if ((endLabel & 0x80000000) != 0x80000000) + throw new IllegalArgumentException("Bad endLabel"); + if ((handlerLabel & 0x80000000) != 0x80000000) + throw new IllegalArgumentException("Bad handlerLabel"); + + /* + * If catchClassName is null, use 0 for the catch_type_index; which + * means catch everything. (Even when the verifier has let you throw + * something other than a Throwable.) + */ + short catch_type_index = (catchClassName == null) + ? 0 + : itsConstantPool.addClass(catchClassName); + ExceptionTableEntry newEntry = new ExceptionTableEntry( + startLabel, + endLabel, + handlerLabel, + catch_type_index); + int N = itsExceptionTableTop; + if (N == 0) { + itsExceptionTable = new ExceptionTableEntry[ExceptionTableSize]; + } else if (N == itsExceptionTable.length) { + ExceptionTableEntry[] tmp = new ExceptionTableEntry[N * 2]; + System.arraycopy(itsExceptionTable, 0, tmp, 0, N); + itsExceptionTable = tmp; + } + itsExceptionTable[N] = newEntry; + itsExceptionTableTop = N + 1; + + } + + public void addLineNumberEntry(short lineNumber) { + if (itsCurrentMethod == null) + throw new IllegalArgumentException("No method to stop"); + int N = itsLineNumberTableTop; + if (N == 0) { + itsLineNumberTable = new int[LineNumberTableSize]; + } else if (N == itsLineNumberTable.length) { + int[] tmp = new int[N * 2]; + System.arraycopy(itsLineNumberTable, 0, tmp, 0, N); + itsLineNumberTable = tmp; + } + itsLineNumberTable[N] = (itsCodeBufferTop << 16) + lineNumber; + itsLineNumberTableTop = N + 1; + } + + /** + * Write the class file to the OutputStream. + * + * @param oStream the stream to write to + * @throws IOException if writing to the stream produces an exception + */ + public void write(OutputStream oStream) + throws IOException + { + byte[] array = toByteArray(); + oStream.write(array); + } + + private int getWriteSize() + { + int size = 0; + + if (itsSourceFileNameIndex != 0) { + itsConstantPool.addUtf8("SourceFile"); + } + + size += 8; //writeLong(FileHeaderConstant); + size += itsConstantPool.getWriteSize(); + size += 2; //writeShort(itsFlags); + size += 2; //writeShort(itsThisClassIndex); + size += 2; //writeShort(itsSuperClassIndex); + size += 2; //writeShort(itsInterfaces.size()); + size += 2 * itsInterfaces.size(); + + size += 2; //writeShort(itsFields.size()); + for (int i = 0; i < itsFields.size(); i++) { + size += ((ClassFileField)(itsFields.get(i))).getWriteSize(); + } + + size += 2; //writeShort(itsMethods.size()); + for (int i = 0; i < itsMethods.size(); i++) { + size += ((ClassFileMethod)(itsMethods.get(i))).getWriteSize(); + } + + if (itsSourceFileNameIndex != 0) { + size += 2; //writeShort(1); attributes count + size += 2; //writeShort(sourceFileAttributeNameIndex); + size += 4; //writeInt(2); + size += 2; //writeShort(itsSourceFileNameIndex); + }else { + size += 2; //out.writeShort(0); no attributes + } + + return size; + } + + /** + * Get the class file as array of bytesto the OutputStream. + */ + public byte[] toByteArray() + { + int dataSize = getWriteSize(); + byte[] data = new byte[dataSize]; + int offset = 0; + + short sourceFileAttributeNameIndex = 0; + if (itsSourceFileNameIndex != 0) { + sourceFileAttributeNameIndex = itsConstantPool.addUtf8( + "SourceFile"); + } + + offset = putInt64(FileHeaderConstant, data, offset); + offset = itsConstantPool.write(data, offset); + offset = putInt16(itsFlags, data, offset); + offset = putInt16(itsThisClassIndex, data, offset); + offset = putInt16(itsSuperClassIndex, data, offset); + offset = putInt16(itsInterfaces.size(), data, offset); + for (int i = 0; i < itsInterfaces.size(); i++) { + int interfaceIndex = ((Short)(itsInterfaces.get(i))).shortValue(); + offset = putInt16(interfaceIndex, data, offset); + } + offset = putInt16(itsFields.size(), data, offset); + for (int i = 0; i < itsFields.size(); i++) { + ClassFileField field = (ClassFileField)itsFields.get(i); + offset = field.write(data, offset); + } + offset = putInt16(itsMethods.size(), data, offset); + for (int i = 0; i < itsMethods.size(); i++) { + ClassFileMethod method = (ClassFileMethod)itsMethods.get(i); + offset = method.write(data, offset); + } + if (itsSourceFileNameIndex != 0) { + offset = putInt16(1, data, offset); // attributes count + offset = putInt16(sourceFileAttributeNameIndex, data, offset); + offset = putInt32(2, data, offset); + offset = putInt16(itsSourceFileNameIndex, data, offset); + } else { + offset = putInt16(0, data, offset); // no attributes + } + + if (offset != dataSize) { + // Check getWriteSize is consistent with write! + throw new RuntimeException(); + } + + return data; + } + + static int putInt64(long value, byte[] array, int offset) + { + offset = putInt32((int)(value >>> 32), array, offset); + return putInt32((int)value, array, offset); + } + + private static void badStack(int value) + { + String s; + if (value < 0) { s = "Stack underflow: "+value; } + else { s = "Too big stack: "+value; } + throw new IllegalStateException(s); + } + + /* + Really weird. Returns an int with # parameters in hi 16 bits, and + stack difference removal of parameters from stack and pushing the + result (it does not take into account removal of this in case of + non-static methods). + If Java really supported references we wouldn't have to be this + perverted. + */ + private static int sizeOfParameters(String pString) + { + int length = pString.length(); + int rightParenthesis = pString.lastIndexOf(')'); + if (3 <= length /* minimal signature takes at least 3 chars: ()V */ + && pString.charAt(0) == '(' + && 1 <= rightParenthesis && rightParenthesis + 1 < length) + { + boolean ok = true; + int index = 1; + int stackDiff = 0; + int count = 0; + stringLoop: + while (index != rightParenthesis) { + switch (pString.charAt(index)) { + default: + ok = false; + break stringLoop; + case 'J' : + case 'D' : + --stackDiff; + // fall thru + case 'B' : + case 'S' : + case 'C' : + case 'I' : + case 'Z' : + case 'F' : + --stackDiff; + ++count; + ++index; + continue; + case '[' : + ++index; + int c = pString.charAt(index); + while (c == '[') { + ++index; + c = pString.charAt(index); + } + switch (c) { + default: + ok = false; + break stringLoop; + case 'J' : + case 'D' : + case 'B' : + case 'S' : + case 'C' : + case 'I' : + case 'Z' : + case 'F' : + --stackDiff; + ++count; + ++index; + continue; + case 'L': + // fall thru + } + // fall thru + case 'L' : { + --stackDiff; + ++count; + ++index; + int semicolon = pString.indexOf(';', index); + if (!(index + 1 <= semicolon + && semicolon < rightParenthesis)) + { + ok = false; + break stringLoop; + } + index = semicolon + 1; + continue; + } + } + } + if (ok) { + switch (pString.charAt(rightParenthesis + 1)) { + default: + ok = false; + break; + case 'J' : + case 'D' : + ++stackDiff; + // fall thru + case 'B' : + case 'S' : + case 'C' : + case 'I' : + case 'Z' : + case 'F' : + case 'L' : + case '[' : + ++stackDiff; + // fall thru + case 'V' : + break; + } + if (ok) { + return ((count << 16) | (0xFFFF & stackDiff)); + } + } + } + throw new IllegalArgumentException( + "Bad parameter signature: "+pString); + } + + static int putInt16(int value, byte[] array, int offset) + { + array[offset + 0] = (byte)(value >>> 8); + array[offset + 1] = (byte)value; + return offset + 2; + } + + static int putInt32(int value, byte[] array, int offset) + { + array[offset + 0] = (byte)(value >>> 24); + array[offset + 1] = (byte)(value >>> 16); + array[offset + 2] = (byte)(value >>> 8); + array[offset + 3] = (byte)value; + return offset + 4; + } + + /** + * Number of operands accompanying the opcode. + */ + static int opcodeCount(int opcode) + { + switch (opcode) { + case ByteCode.AALOAD: + case ByteCode.AASTORE: + case ByteCode.ACONST_NULL: + case ByteCode.ALOAD_0: + case ByteCode.ALOAD_1: + case ByteCode.ALOAD_2: + case ByteCode.ALOAD_3: + case ByteCode.ARETURN: + case ByteCode.ARRAYLENGTH: + case ByteCode.ASTORE_0: + case ByteCode.ASTORE_1: + case ByteCode.ASTORE_2: + case ByteCode.ASTORE_3: + case ByteCode.ATHROW: + case ByteCode.BALOAD: + case ByteCode.BASTORE: + case ByteCode.BREAKPOINT: + case ByteCode.CALOAD: + case ByteCode.CASTORE: + case ByteCode.D2F: + case ByteCode.D2I: + case ByteCode.D2L: + case ByteCode.DADD: + case ByteCode.DALOAD: + case ByteCode.DASTORE: + case ByteCode.DCMPG: + case ByteCode.DCMPL: + case ByteCode.DCONST_0: + case ByteCode.DCONST_1: + case ByteCode.DDIV: + case ByteCode.DLOAD_0: + case ByteCode.DLOAD_1: + case ByteCode.DLOAD_2: + case ByteCode.DLOAD_3: + case ByteCode.DMUL: + case ByteCode.DNEG: + case ByteCode.DREM: + case ByteCode.DRETURN: + case ByteCode.DSTORE_0: + case ByteCode.DSTORE_1: + case ByteCode.DSTORE_2: + case ByteCode.DSTORE_3: + case ByteCode.DSUB: + case ByteCode.DUP: + case ByteCode.DUP2: + case ByteCode.DUP2_X1: + case ByteCode.DUP2_X2: + case ByteCode.DUP_X1: + case ByteCode.DUP_X2: + case ByteCode.F2D: + case ByteCode.F2I: + case ByteCode.F2L: + case ByteCode.FADD: + case ByteCode.FALOAD: + case ByteCode.FASTORE: + case ByteCode.FCMPG: + case ByteCode.FCMPL: + case ByteCode.FCONST_0: + case ByteCode.FCONST_1: + case ByteCode.FCONST_2: + case ByteCode.FDIV: + case ByteCode.FLOAD_0: + case ByteCode.FLOAD_1: + case ByteCode.FLOAD_2: + case ByteCode.FLOAD_3: + case ByteCode.FMUL: + case ByteCode.FNEG: + case ByteCode.FREM: + case ByteCode.FRETURN: + case ByteCode.FSTORE_0: + case ByteCode.FSTORE_1: + case ByteCode.FSTORE_2: + case ByteCode.FSTORE_3: + case ByteCode.FSUB: + case ByteCode.I2B: + case ByteCode.I2C: + case ByteCode.I2D: + case ByteCode.I2F: + case ByteCode.I2L: + case ByteCode.I2S: + case ByteCode.IADD: + case ByteCode.IALOAD: + case ByteCode.IAND: + case ByteCode.IASTORE: + case ByteCode.ICONST_0: + case ByteCode.ICONST_1: + case ByteCode.ICONST_2: + case ByteCode.ICONST_3: + case ByteCode.ICONST_4: + case ByteCode.ICONST_5: + case ByteCode.ICONST_M1: + case ByteCode.IDIV: + case ByteCode.ILOAD_0: + case ByteCode.ILOAD_1: + case ByteCode.ILOAD_2: + case ByteCode.ILOAD_3: + case ByteCode.IMPDEP1: + case ByteCode.IMPDEP2: + case ByteCode.IMUL: + case ByteCode.INEG: + case ByteCode.IOR: + case ByteCode.IREM: + case ByteCode.IRETURN: + case ByteCode.ISHL: + case ByteCode.ISHR: + case ByteCode.ISTORE_0: + case ByteCode.ISTORE_1: + case ByteCode.ISTORE_2: + case ByteCode.ISTORE_3: + case ByteCode.ISUB: + case ByteCode.IUSHR: + case ByteCode.IXOR: + case ByteCode.L2D: + case ByteCode.L2F: + case ByteCode.L2I: + case ByteCode.LADD: + case ByteCode.LALOAD: + case ByteCode.LAND: + case ByteCode.LASTORE: + case ByteCode.LCMP: + case ByteCode.LCONST_0: + case ByteCode.LCONST_1: + case ByteCode.LDIV: + case ByteCode.LLOAD_0: + case ByteCode.LLOAD_1: + case ByteCode.LLOAD_2: + case ByteCode.LLOAD_3: + case ByteCode.LMUL: + case ByteCode.LNEG: + case ByteCode.LOR: + case ByteCode.LREM: + case ByteCode.LRETURN: + case ByteCode.LSHL: + case ByteCode.LSHR: + case ByteCode.LSTORE_0: + case ByteCode.LSTORE_1: + case ByteCode.LSTORE_2: + case ByteCode.LSTORE_3: + case ByteCode.LSUB: + case ByteCode.LUSHR: + case ByteCode.LXOR: + case ByteCode.MONITORENTER: + case ByteCode.MONITOREXIT: + case ByteCode.NOP: + case ByteCode.POP: + case ByteCode.POP2: + case ByteCode.RETURN: + case ByteCode.SALOAD: + case ByteCode.SASTORE: + case ByteCode.SWAP: + case ByteCode.WIDE: + return 0; + case ByteCode.ALOAD: + case ByteCode.ANEWARRAY: + case ByteCode.ASTORE: + case ByteCode.BIPUSH: + case ByteCode.CHECKCAST: + case ByteCode.DLOAD: + case ByteCode.DSTORE: + case ByteCode.FLOAD: + case ByteCode.FSTORE: + case ByteCode.GETFIELD: + case ByteCode.GETSTATIC: + case ByteCode.GOTO: + case ByteCode.GOTO_W: + case ByteCode.IFEQ: + case ByteCode.IFGE: + case ByteCode.IFGT: + case ByteCode.IFLE: + case ByteCode.IFLT: + case ByteCode.IFNE: + case ByteCode.IFNONNULL: + case ByteCode.IFNULL: + case ByteCode.IF_ACMPEQ: + case ByteCode.IF_ACMPNE: + case ByteCode.IF_ICMPEQ: + case ByteCode.IF_ICMPGE: + case ByteCode.IF_ICMPGT: + case ByteCode.IF_ICMPLE: + case ByteCode.IF_ICMPLT: + case ByteCode.IF_ICMPNE: + case ByteCode.ILOAD: + case ByteCode.INSTANCEOF: + case ByteCode.INVOKEINTERFACE: + case ByteCode.INVOKESPECIAL: + case ByteCode.INVOKESTATIC: + case ByteCode.INVOKEVIRTUAL: + case ByteCode.ISTORE: + case ByteCode.JSR: + case ByteCode.JSR_W: + case ByteCode.LDC: + case ByteCode.LDC2_W: + case ByteCode.LDC_W: + case ByteCode.LLOAD: + case ByteCode.LSTORE: + case ByteCode.NEW: + case ByteCode.NEWARRAY: + case ByteCode.PUTFIELD: + case ByteCode.PUTSTATIC: + case ByteCode.RET: + case ByteCode.SIPUSH: + return 1; + + case ByteCode.IINC: + case ByteCode.MULTIANEWARRAY: + return 2; + + case ByteCode.LOOKUPSWITCH: + case ByteCode.TABLESWITCH: + return -1; + } + throw new IllegalArgumentException("Bad opcode: "+opcode); + } + + /** + * The effect on the operand stack of a given opcode. + */ + static int stackChange(int opcode) + { + // For INVOKE... accounts only for popping this (unless static), + // ignoring parameters and return type + switch (opcode) { + case ByteCode.DASTORE: + case ByteCode.LASTORE: + return -4; + + case ByteCode.AASTORE: + case ByteCode.BASTORE: + case ByteCode.CASTORE: + case ByteCode.DCMPG: + case ByteCode.DCMPL: + case ByteCode.FASTORE: + case ByteCode.IASTORE: + case ByteCode.LCMP: + case ByteCode.SASTORE: + return -3; + + case ByteCode.DADD: + case ByteCode.DDIV: + case ByteCode.DMUL: + case ByteCode.DREM: + case ByteCode.DRETURN: + case ByteCode.DSTORE: + case ByteCode.DSTORE_0: + case ByteCode.DSTORE_1: + case ByteCode.DSTORE_2: + case ByteCode.DSTORE_3: + case ByteCode.DSUB: + case ByteCode.IF_ACMPEQ: + case ByteCode.IF_ACMPNE: + case ByteCode.IF_ICMPEQ: + case ByteCode.IF_ICMPGE: + case ByteCode.IF_ICMPGT: + case ByteCode.IF_ICMPLE: + case ByteCode.IF_ICMPLT: + case ByteCode.IF_ICMPNE: + case ByteCode.LADD: + case ByteCode.LAND: + case ByteCode.LDIV: + case ByteCode.LMUL: + case ByteCode.LOR: + case ByteCode.LREM: + case ByteCode.LRETURN: + case ByteCode.LSTORE: + case ByteCode.LSTORE_0: + case ByteCode.LSTORE_1: + case ByteCode.LSTORE_2: + case ByteCode.LSTORE_3: + case ByteCode.LSUB: + case ByteCode.LXOR: + case ByteCode.POP2: + return -2; + + case ByteCode.AALOAD: + case ByteCode.ARETURN: + case ByteCode.ASTORE: + case ByteCode.ASTORE_0: + case ByteCode.ASTORE_1: + case ByteCode.ASTORE_2: + case ByteCode.ASTORE_3: + case ByteCode.ATHROW: + case ByteCode.BALOAD: + case ByteCode.CALOAD: + case ByteCode.D2F: + case ByteCode.D2I: + case ByteCode.FADD: + case ByteCode.FALOAD: + case ByteCode.FCMPG: + case ByteCode.FCMPL: + case ByteCode.FDIV: + case ByteCode.FMUL: + case ByteCode.FREM: + case ByteCode.FRETURN: + case ByteCode.FSTORE: + case ByteCode.FSTORE_0: + case ByteCode.FSTORE_1: + case ByteCode.FSTORE_2: + case ByteCode.FSTORE_3: + case ByteCode.FSUB: + case ByteCode.GETFIELD: + case ByteCode.IADD: + case ByteCode.IALOAD: + case ByteCode.IAND: + case ByteCode.IDIV: + case ByteCode.IFEQ: + case ByteCode.IFGE: + case ByteCode.IFGT: + case ByteCode.IFLE: + case ByteCode.IFLT: + case ByteCode.IFNE: + case ByteCode.IFNONNULL: + case ByteCode.IFNULL: + case ByteCode.IMUL: + case ByteCode.INVOKEINTERFACE: // + case ByteCode.INVOKESPECIAL: // but needs to account for + case ByteCode.INVOKEVIRTUAL: // pops 'this' (unless static) + case ByteCode.IOR: + case ByteCode.IREM: + case ByteCode.IRETURN: + case ByteCode.ISHL: + case ByteCode.ISHR: + case ByteCode.ISTORE: + case ByteCode.ISTORE_0: + case ByteCode.ISTORE_1: + case ByteCode.ISTORE_2: + case ByteCode.ISTORE_3: + case ByteCode.ISUB: + case ByteCode.IUSHR: + case ByteCode.IXOR: + case ByteCode.L2F: + case ByteCode.L2I: + case ByteCode.LOOKUPSWITCH: + case ByteCode.LSHL: + case ByteCode.LSHR: + case ByteCode.LUSHR: + case ByteCode.MONITORENTER: + case ByteCode.MONITOREXIT: + case ByteCode.POP: + case ByteCode.PUTFIELD: + case ByteCode.SALOAD: + case ByteCode.TABLESWITCH: + return -1; + + case ByteCode.ANEWARRAY: + case ByteCode.ARRAYLENGTH: + case ByteCode.BREAKPOINT: + case ByteCode.CHECKCAST: + case ByteCode.D2L: + case ByteCode.DALOAD: + case ByteCode.DNEG: + case ByteCode.F2I: + case ByteCode.FNEG: + case ByteCode.GETSTATIC: + case ByteCode.GOTO: + case ByteCode.GOTO_W: + case ByteCode.I2B: + case ByteCode.I2C: + case ByteCode.I2F: + case ByteCode.I2S: + case ByteCode.IINC: + case ByteCode.IMPDEP1: + case ByteCode.IMPDEP2: + case ByteCode.INEG: + case ByteCode.INSTANCEOF: + case ByteCode.INVOKESTATIC: + case ByteCode.L2D: + case ByteCode.LALOAD: + case ByteCode.LNEG: + case ByteCode.NEWARRAY: + case ByteCode.NOP: + case ByteCode.PUTSTATIC: + case ByteCode.RET: + case ByteCode.RETURN: + case ByteCode.SWAP: + case ByteCode.WIDE: + return 0; + + case ByteCode.ACONST_NULL: + case ByteCode.ALOAD: + case ByteCode.ALOAD_0: + case ByteCode.ALOAD_1: + case ByteCode.ALOAD_2: + case ByteCode.ALOAD_3: + case ByteCode.BIPUSH: + case ByteCode.DUP: + case ByteCode.DUP_X1: + case ByteCode.DUP_X2: + case ByteCode.F2D: + case ByteCode.F2L: + case ByteCode.FCONST_0: + case ByteCode.FCONST_1: + case ByteCode.FCONST_2: + case ByteCode.FLOAD: + case ByteCode.FLOAD_0: + case ByteCode.FLOAD_1: + case ByteCode.FLOAD_2: + case ByteCode.FLOAD_3: + case ByteCode.I2D: + case ByteCode.I2L: + case ByteCode.ICONST_0: + case ByteCode.ICONST_1: + case ByteCode.ICONST_2: + case ByteCode.ICONST_3: + case ByteCode.ICONST_4: + case ByteCode.ICONST_5: + case ByteCode.ICONST_M1: + case ByteCode.ILOAD: + case ByteCode.ILOAD_0: + case ByteCode.ILOAD_1: + case ByteCode.ILOAD_2: + case ByteCode.ILOAD_3: + case ByteCode.JSR: + case ByteCode.JSR_W: + case ByteCode.LDC: + case ByteCode.LDC_W: + case ByteCode.MULTIANEWARRAY: + case ByteCode.NEW: + case ByteCode.SIPUSH: + return 1; + + case ByteCode.DCONST_0: + case ByteCode.DCONST_1: + case ByteCode.DLOAD: + case ByteCode.DLOAD_0: + case ByteCode.DLOAD_1: + case ByteCode.DLOAD_2: + case ByteCode.DLOAD_3: + case ByteCode.DUP2: + case ByteCode.DUP2_X1: + case ByteCode.DUP2_X2: + case ByteCode.LCONST_0: + case ByteCode.LCONST_1: + case ByteCode.LDC2_W: + case ByteCode.LLOAD: + case ByteCode.LLOAD_0: + case ByteCode.LLOAD_1: + case ByteCode.LLOAD_2: + case ByteCode.LLOAD_3: + return 2; + } + throw new IllegalArgumentException("Bad opcode: "+opcode); + } + + /* + * Number of bytes of operands generated after the opcode. + * Not in use currently. + */ +/* + int extra(int opcode) + { + switch (opcode) { + case ByteCode.AALOAD: + case ByteCode.AASTORE: + case ByteCode.ACONST_NULL: + case ByteCode.ALOAD_0: + case ByteCode.ALOAD_1: + case ByteCode.ALOAD_2: + case ByteCode.ALOAD_3: + case ByteCode.ARETURN: + case ByteCode.ARRAYLENGTH: + case ByteCode.ASTORE_0: + case ByteCode.ASTORE_1: + case ByteCode.ASTORE_2: + case ByteCode.ASTORE_3: + case ByteCode.ATHROW: + case ByteCode.BALOAD: + case ByteCode.BASTORE: + case ByteCode.BREAKPOINT: + case ByteCode.CALOAD: + case ByteCode.CASTORE: + case ByteCode.D2F: + case ByteCode.D2I: + case ByteCode.D2L: + case ByteCode.DADD: + case ByteCode.DALOAD: + case ByteCode.DASTORE: + case ByteCode.DCMPG: + case ByteCode.DCMPL: + case ByteCode.DCONST_0: + case ByteCode.DCONST_1: + case ByteCode.DDIV: + case ByteCode.DLOAD_0: + case ByteCode.DLOAD_1: + case ByteCode.DLOAD_2: + case ByteCode.DLOAD_3: + case ByteCode.DMUL: + case ByteCode.DNEG: + case ByteCode.DREM: + case ByteCode.DRETURN: + case ByteCode.DSTORE_0: + case ByteCode.DSTORE_1: + case ByteCode.DSTORE_2: + case ByteCode.DSTORE_3: + case ByteCode.DSUB: + case ByteCode.DUP2: + case ByteCode.DUP2_X1: + case ByteCode.DUP2_X2: + case ByteCode.DUP: + case ByteCode.DUP_X1: + case ByteCode.DUP_X2: + case ByteCode.F2D: + case ByteCode.F2I: + case ByteCode.F2L: + case ByteCode.FADD: + case ByteCode.FALOAD: + case ByteCode.FASTORE: + case ByteCode.FCMPG: + case ByteCode.FCMPL: + case ByteCode.FCONST_0: + case ByteCode.FCONST_1: + case ByteCode.FCONST_2: + case ByteCode.FDIV: + case ByteCode.FLOAD_0: + case ByteCode.FLOAD_1: + case ByteCode.FLOAD_2: + case ByteCode.FLOAD_3: + case ByteCode.FMUL: + case ByteCode.FNEG: + case ByteCode.FREM: + case ByteCode.FRETURN: + case ByteCode.FSTORE_0: + case ByteCode.FSTORE_1: + case ByteCode.FSTORE_2: + case ByteCode.FSTORE_3: + case ByteCode.FSUB: + case ByteCode.I2B: + case ByteCode.I2C: + case ByteCode.I2D: + case ByteCode.I2F: + case ByteCode.I2L: + case ByteCode.I2S: + case ByteCode.IADD: + case ByteCode.IALOAD: + case ByteCode.IAND: + case ByteCode.IASTORE: + case ByteCode.ICONST_0: + case ByteCode.ICONST_1: + case ByteCode.ICONST_2: + case ByteCode.ICONST_3: + case ByteCode.ICONST_4: + case ByteCode.ICONST_5: + case ByteCode.ICONST_M1: + case ByteCode.IDIV: + case ByteCode.ILOAD_0: + case ByteCode.ILOAD_1: + case ByteCode.ILOAD_2: + case ByteCode.ILOAD_3: + case ByteCode.IMPDEP1: + case ByteCode.IMPDEP2: + case ByteCode.IMUL: + case ByteCode.INEG: + case ByteCode.IOR: + case ByteCode.IREM: + case ByteCode.IRETURN: + case ByteCode.ISHL: + case ByteCode.ISHR: + case ByteCode.ISTORE_0: + case ByteCode.ISTORE_1: + case ByteCode.ISTORE_2: + case ByteCode.ISTORE_3: + case ByteCode.ISUB: + case ByteCode.IUSHR: + case ByteCode.IXOR: + case ByteCode.L2D: + case ByteCode.L2F: + case ByteCode.L2I: + case ByteCode.LADD: + case ByteCode.LALOAD: + case ByteCode.LAND: + case ByteCode.LASTORE: + case ByteCode.LCMP: + case ByteCode.LCONST_0: + case ByteCode.LCONST_1: + case ByteCode.LDIV: + case ByteCode.LLOAD_0: + case ByteCode.LLOAD_1: + case ByteCode.LLOAD_2: + case ByteCode.LLOAD_3: + case ByteCode.LMUL: + case ByteCode.LNEG: + case ByteCode.LOR: + case ByteCode.LREM: + case ByteCode.LRETURN: + case ByteCode.LSHL: + case ByteCode.LSHR: + case ByteCode.LSTORE_0: + case ByteCode.LSTORE_1: + case ByteCode.LSTORE_2: + case ByteCode.LSTORE_3: + case ByteCode.LSUB: + case ByteCode.LUSHR: + case ByteCode.LXOR: + case ByteCode.MONITORENTER: + case ByteCode.MONITOREXIT: + case ByteCode.NOP: + case ByteCode.POP2: + case ByteCode.POP: + case ByteCode.RETURN: + case ByteCode.SALOAD: + case ByteCode.SASTORE: + case ByteCode.SWAP: + case ByteCode.WIDE: + return 0; + + case ByteCode.ALOAD: + case ByteCode.ASTORE: + case ByteCode.BIPUSH: + case ByteCode.DLOAD: + case ByteCode.DSTORE: + case ByteCode.FLOAD: + case ByteCode.FSTORE: + case ByteCode.ILOAD: + case ByteCode.ISTORE: + case ByteCode.LDC: + case ByteCode.LLOAD: + case ByteCode.LSTORE: + case ByteCode.NEWARRAY: + case ByteCode.RET: + return 1; + + case ByteCode.ANEWARRAY: + case ByteCode.CHECKCAST: + case ByteCode.GETFIELD: + case ByteCode.GETSTATIC: + case ByteCode.GOTO: + case ByteCode.IFEQ: + case ByteCode.IFGE: + case ByteCode.IFGT: + case ByteCode.IFLE: + case ByteCode.IFLT: + case ByteCode.IFNE: + case ByteCode.IFNONNULL: + case ByteCode.IFNULL: + case ByteCode.IF_ACMPEQ: + case ByteCode.IF_ACMPNE: + case ByteCode.IF_ICMPEQ: + case ByteCode.IF_ICMPGE: + case ByteCode.IF_ICMPGT: + case ByteCode.IF_ICMPLE: + case ByteCode.IF_ICMPLT: + case ByteCode.IF_ICMPNE: + case ByteCode.IINC: + case ByteCode.INSTANCEOF: + case ByteCode.INVOKEINTERFACE: + case ByteCode.INVOKESPECIAL: + case ByteCode.INVOKESTATIC: + case ByteCode.INVOKEVIRTUAL: + case ByteCode.JSR: + case ByteCode.LDC2_W: + case ByteCode.LDC_W: + case ByteCode.NEW: + case ByteCode.PUTFIELD: + case ByteCode.PUTSTATIC: + case ByteCode.SIPUSH: + return 2; + + case ByteCode.MULTIANEWARRAY: + return 3; + + case ByteCode.GOTO_W: + case ByteCode.JSR_W: + return 4; + + case ByteCode.LOOKUPSWITCH: // depends on alignment + case ByteCode.TABLESWITCH: // depends on alignment + return -1; + } + throw new IllegalArgumentException("Bad opcode: "+opcode); + } +*/ + private static String bytecodeStr(int code) + { + if (DEBUGSTACK || DEBUGCODE) { + switch (code) { + case ByteCode.NOP: return "nop"; + case ByteCode.ACONST_NULL: return "aconst_null"; + case ByteCode.ICONST_M1: return "iconst_m1"; + case ByteCode.ICONST_0: return "iconst_0"; + case ByteCode.ICONST_1: return "iconst_1"; + case ByteCode.ICONST_2: return "iconst_2"; + case ByteCode.ICONST_3: return "iconst_3"; + case ByteCode.ICONST_4: return "iconst_4"; + case ByteCode.ICONST_5: return "iconst_5"; + case ByteCode.LCONST_0: return "lconst_0"; + case ByteCode.LCONST_1: return "lconst_1"; + case ByteCode.FCONST_0: return "fconst_0"; + case ByteCode.FCONST_1: return "fconst_1"; + case ByteCode.FCONST_2: return "fconst_2"; + case ByteCode.DCONST_0: return "dconst_0"; + case ByteCode.DCONST_1: return "dconst_1"; + case ByteCode.BIPUSH: return "bipush"; + case ByteCode.SIPUSH: return "sipush"; + case ByteCode.LDC: return "ldc"; + case ByteCode.LDC_W: return "ldc_w"; + case ByteCode.LDC2_W: return "ldc2_w"; + case ByteCode.ILOAD: return "iload"; + case ByteCode.LLOAD: return "lload"; + case ByteCode.FLOAD: return "fload"; + case ByteCode.DLOAD: return "dload"; + case ByteCode.ALOAD: return "aload"; + case ByteCode.ILOAD_0: return "iload_0"; + case ByteCode.ILOAD_1: return "iload_1"; + case ByteCode.ILOAD_2: return "iload_2"; + case ByteCode.ILOAD_3: return "iload_3"; + case ByteCode.LLOAD_0: return "lload_0"; + case ByteCode.LLOAD_1: return "lload_1"; + case ByteCode.LLOAD_2: return "lload_2"; + case ByteCode.LLOAD_3: return "lload_3"; + case ByteCode.FLOAD_0: return "fload_0"; + case ByteCode.FLOAD_1: return "fload_1"; + case ByteCode.FLOAD_2: return "fload_2"; + case ByteCode.FLOAD_3: return "fload_3"; + case ByteCode.DLOAD_0: return "dload_0"; + case ByteCode.DLOAD_1: return "dload_1"; + case ByteCode.DLOAD_2: return "dload_2"; + case ByteCode.DLOAD_3: return "dload_3"; + case ByteCode.ALOAD_0: return "aload_0"; + case ByteCode.ALOAD_1: return "aload_1"; + case ByteCode.ALOAD_2: return "aload_2"; + case ByteCode.ALOAD_3: return "aload_3"; + case ByteCode.IALOAD: return "iaload"; + case ByteCode.LALOAD: return "laload"; + case ByteCode.FALOAD: return "faload"; + case ByteCode.DALOAD: return "daload"; + case ByteCode.AALOAD: return "aaload"; + case ByteCode.BALOAD: return "baload"; + case ByteCode.CALOAD: return "caload"; + case ByteCode.SALOAD: return "saload"; + case ByteCode.ISTORE: return "istore"; + case ByteCode.LSTORE: return "lstore"; + case ByteCode.FSTORE: return "fstore"; + case ByteCode.DSTORE: return "dstore"; + case ByteCode.ASTORE: return "astore"; + case ByteCode.ISTORE_0: return "istore_0"; + case ByteCode.ISTORE_1: return "istore_1"; + case ByteCode.ISTORE_2: return "istore_2"; + case ByteCode.ISTORE_3: return "istore_3"; + case ByteCode.LSTORE_0: return "lstore_0"; + case ByteCode.LSTORE_1: return "lstore_1"; + case ByteCode.LSTORE_2: return "lstore_2"; + case ByteCode.LSTORE_3: return "lstore_3"; + case ByteCode.FSTORE_0: return "fstore_0"; + case ByteCode.FSTORE_1: return "fstore_1"; + case ByteCode.FSTORE_2: return "fstore_2"; + case ByteCode.FSTORE_3: return "fstore_3"; + case ByteCode.DSTORE_0: return "dstore_0"; + case ByteCode.DSTORE_1: return "dstore_1"; + case ByteCode.DSTORE_2: return "dstore_2"; + case ByteCode.DSTORE_3: return "dstore_3"; + case ByteCode.ASTORE_0: return "astore_0"; + case ByteCode.ASTORE_1: return "astore_1"; + case ByteCode.ASTORE_2: return "astore_2"; + case ByteCode.ASTORE_3: return "astore_3"; + case ByteCode.IASTORE: return "iastore"; + case ByteCode.LASTORE: return "lastore"; + case ByteCode.FASTORE: return "fastore"; + case ByteCode.DASTORE: return "dastore"; + case ByteCode.AASTORE: return "aastore"; + case ByteCode.BASTORE: return "bastore"; + case ByteCode.CASTORE: return "castore"; + case ByteCode.SASTORE: return "sastore"; + case ByteCode.POP: return "pop"; + case ByteCode.POP2: return "pop2"; + case ByteCode.DUP: return "dup"; + case ByteCode.DUP_X1: return "dup_x1"; + case ByteCode.DUP_X2: return "dup_x2"; + case ByteCode.DUP2: return "dup2"; + case ByteCode.DUP2_X1: return "dup2_x1"; + case ByteCode.DUP2_X2: return "dup2_x2"; + case ByteCode.SWAP: return "swap"; + case ByteCode.IADD: return "iadd"; + case ByteCode.LADD: return "ladd"; + case ByteCode.FADD: return "fadd"; + case ByteCode.DADD: return "dadd"; + case ByteCode.ISUB: return "isub"; + case ByteCode.LSUB: return "lsub"; + case ByteCode.FSUB: return "fsub"; + case ByteCode.DSUB: return "dsub"; + case ByteCode.IMUL: return "imul"; + case ByteCode.LMUL: return "lmul"; + case ByteCode.FMUL: return "fmul"; + case ByteCode.DMUL: return "dmul"; + case ByteCode.IDIV: return "idiv"; + case ByteCode.LDIV: return "ldiv"; + case ByteCode.FDIV: return "fdiv"; + case ByteCode.DDIV: return "ddiv"; + case ByteCode.IREM: return "irem"; + case ByteCode.LREM: return "lrem"; + case ByteCode.FREM: return "frem"; + case ByteCode.DREM: return "drem"; + case ByteCode.INEG: return "ineg"; + case ByteCode.LNEG: return "lneg"; + case ByteCode.FNEG: return "fneg"; + case ByteCode.DNEG: return "dneg"; + case ByteCode.ISHL: return "ishl"; + case ByteCode.LSHL: return "lshl"; + case ByteCode.ISHR: return "ishr"; + case ByteCode.LSHR: return "lshr"; + case ByteCode.IUSHR: return "iushr"; + case ByteCode.LUSHR: return "lushr"; + case ByteCode.IAND: return "iand"; + case ByteCode.LAND: return "land"; + case ByteCode.IOR: return "ior"; + case ByteCode.LOR: return "lor"; + case ByteCode.IXOR: return "ixor"; + case ByteCode.LXOR: return "lxor"; + case ByteCode.IINC: return "iinc"; + case ByteCode.I2L: return "i2l"; + case ByteCode.I2F: return "i2f"; + case ByteCode.I2D: return "i2d"; + case ByteCode.L2I: return "l2i"; + case ByteCode.L2F: return "l2f"; + case ByteCode.L2D: return "l2d"; + case ByteCode.F2I: return "f2i"; + case ByteCode.F2L: return "f2l"; + case ByteCode.F2D: return "f2d"; + case ByteCode.D2I: return "d2i"; + case ByteCode.D2L: return "d2l"; + case ByteCode.D2F: return "d2f"; + case ByteCode.I2B: return "i2b"; + case ByteCode.I2C: return "i2c"; + case ByteCode.I2S: return "i2s"; + case ByteCode.LCMP: return "lcmp"; + case ByteCode.FCMPL: return "fcmpl"; + case ByteCode.FCMPG: return "fcmpg"; + case ByteCode.DCMPL: return "dcmpl"; + case ByteCode.DCMPG: return "dcmpg"; + case ByteCode.IFEQ: return "ifeq"; + case ByteCode.IFNE: return "ifne"; + case ByteCode.IFLT: return "iflt"; + case ByteCode.IFGE: return "ifge"; + case ByteCode.IFGT: return "ifgt"; + case ByteCode.IFLE: return "ifle"; + case ByteCode.IF_ICMPEQ: return "if_icmpeq"; + case ByteCode.IF_ICMPNE: return "if_icmpne"; + case ByteCode.IF_ICMPLT: return "if_icmplt"; + case ByteCode.IF_ICMPGE: return "if_icmpge"; + case ByteCode.IF_ICMPGT: return "if_icmpgt"; + case ByteCode.IF_ICMPLE: return "if_icmple"; + case ByteCode.IF_ACMPEQ: return "if_acmpeq"; + case ByteCode.IF_ACMPNE: return "if_acmpne"; + case ByteCode.GOTO: return "goto"; + case ByteCode.JSR: return "jsr"; + case ByteCode.RET: return "ret"; + case ByteCode.TABLESWITCH: return "tableswitch"; + case ByteCode.LOOKUPSWITCH: return "lookupswitch"; + case ByteCode.IRETURN: return "ireturn"; + case ByteCode.LRETURN: return "lreturn"; + case ByteCode.FRETURN: return "freturn"; + case ByteCode.DRETURN: return "dreturn"; + case ByteCode.ARETURN: return "areturn"; + case ByteCode.RETURN: return "return"; + case ByteCode.GETSTATIC: return "getstatic"; + case ByteCode.PUTSTATIC: return "putstatic"; + case ByteCode.GETFIELD: return "getfield"; + case ByteCode.PUTFIELD: return "putfield"; + case ByteCode.INVOKEVIRTUAL: return "invokevirtual"; + case ByteCode.INVOKESPECIAL: return "invokespecial"; + case ByteCode.INVOKESTATIC: return "invokestatic"; + case ByteCode.INVOKEINTERFACE: return "invokeinterface"; + case ByteCode.NEW: return "new"; + case ByteCode.NEWARRAY: return "newarray"; + case ByteCode.ANEWARRAY: return "anewarray"; + case ByteCode.ARRAYLENGTH: return "arraylength"; + case ByteCode.ATHROW: return "athrow"; + case ByteCode.CHECKCAST: return "checkcast"; + case ByteCode.INSTANCEOF: return "instanceof"; + case ByteCode.MONITORENTER: return "monitorenter"; + case ByteCode.MONITOREXIT: return "monitorexit"; + case ByteCode.WIDE: return "wide"; + case ByteCode.MULTIANEWARRAY: return "multianewarray"; + case ByteCode.IFNULL: return "ifnull"; + case ByteCode.IFNONNULL: return "ifnonnull"; + case ByteCode.GOTO_W: return "goto_w"; + case ByteCode.JSR_W: return "jsr_w"; + case ByteCode.BREAKPOINT: return "breakpoint"; + + case ByteCode.IMPDEP1: return "impdep1"; + case ByteCode.IMPDEP2: return "impdep2"; + } + } + return ""; + } + + final char[] getCharBuffer(int minimalSize) + { + if (minimalSize > tmpCharBuffer.length) { + int newSize = tmpCharBuffer.length * 2; + if (minimalSize > newSize) { newSize = minimalSize; } + tmpCharBuffer = new char[newSize]; + } + return tmpCharBuffer; + } + + private static final int LineNumberTableSize = 16; + private static final int ExceptionTableSize = 4; + + private final static long FileHeaderConstant = 0xCAFEBABE0003002DL; + // Set DEBUG flags to true to get better checking and progress info. + private static final boolean DEBUGSTACK = false; + private static final boolean DEBUGLABELS = false; + private static final boolean DEBUGCODE = false; + + private String generatedClassName; + + private ExceptionTableEntry itsExceptionTable[]; + private int itsExceptionTableTop; + + private int itsLineNumberTable[]; // pack start_pc & line_number together + private int itsLineNumberTableTop; + + private byte[] itsCodeBuffer = new byte[256]; + private int itsCodeBufferTop; + + private ConstantPool itsConstantPool; + + private ClassFileMethod itsCurrentMethod; + private short itsStackTop; + + private short itsMaxStack; + private short itsMaxLocals; + + private ObjArray itsMethods = new ObjArray(); + private ObjArray itsFields = new ObjArray(); + private ObjArray itsInterfaces = new ObjArray(); + + private short itsFlags; + private short itsThisClassIndex; + private short itsSuperClassIndex; + private short itsSourceFileNameIndex; + + private static final int MIN_LABEL_TABLE_SIZE = 32; + private int[] itsLabelTable; + private int itsLabelTableTop; + +// itsFixupTable[i] = (label_index << 32) | fixup_site + private static final int MIN_FIXUP_TABLE_SIZE = 40; + private long[] itsFixupTable; + private int itsFixupTableTop; + private ObjArray itsVarDescriptors; + + private char[] tmpCharBuffer = new char[64]; +} + +final class ExceptionTableEntry +{ + + ExceptionTableEntry(int startLabel, int endLabel, + int handlerLabel, short catchType) + { + itsStartLabel = startLabel; + itsEndLabel = endLabel; + itsHandlerLabel = handlerLabel; + itsCatchType = catchType; + } + + int itsStartLabel; + int itsEndLabel; + int itsHandlerLabel; + short itsCatchType; +} + +final class ClassFileField +{ + + ClassFileField(short nameIndex, short typeIndex, short flags) + { + itsNameIndex = nameIndex; + itsTypeIndex = typeIndex; + itsFlags = flags; + itsHasAttributes = false; + } + + void setAttributes(short attr1, short attr2, short attr3, int index) + { + itsHasAttributes = true; + itsAttr1 = attr1; + itsAttr2 = attr2; + itsAttr3 = attr3; + itsIndex = index; + } + + int write(byte[] data, int offset) + { + offset = ClassFileWriter.putInt16(itsFlags, data, offset); + offset = ClassFileWriter.putInt16(itsNameIndex, data, offset); + offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset); + if (!itsHasAttributes) { + // write 0 attributes + offset = ClassFileWriter.putInt16(0, data, offset); + } else { + offset = ClassFileWriter.putInt16(1, data, offset); + offset = ClassFileWriter.putInt16(itsAttr1, data, offset); + offset = ClassFileWriter.putInt16(itsAttr2, data, offset); + offset = ClassFileWriter.putInt16(itsAttr3, data, offset); + offset = ClassFileWriter.putInt16(itsIndex, data, offset); + } + return offset; + } + + int getWriteSize() + { + int size = 2 * 3; + if (!itsHasAttributes) { + size += 2; + } else { + size += 2 + 2 * 4; + } + return size; + } + + private short itsNameIndex; + private short itsTypeIndex; + private short itsFlags; + private boolean itsHasAttributes; + private short itsAttr1, itsAttr2, itsAttr3; + private int itsIndex; +} + +final class ClassFileMethod +{ + + ClassFileMethod(short nameIndex, short typeIndex, short flags) + { + itsNameIndex = nameIndex; + itsTypeIndex = typeIndex; + itsFlags = flags; + } + + void setCodeAttribute(byte codeAttribute[]) + { + itsCodeAttribute = codeAttribute; + } + + int write(byte[] data, int offset) + { + offset = ClassFileWriter.putInt16(itsFlags, data, offset); + offset = ClassFileWriter.putInt16(itsNameIndex, data, offset); + offset = ClassFileWriter.putInt16(itsTypeIndex, data, offset); + // Code attribute only + offset = ClassFileWriter.putInt16(1, data, offset); + System.arraycopy(itsCodeAttribute, 0, data, offset, + itsCodeAttribute.length); + offset += itsCodeAttribute.length; + return offset; + } + + int getWriteSize() + { + return 2 * 4 + itsCodeAttribute.length; + } + + private short itsNameIndex; + private short itsTypeIndex; + private short itsFlags; + private byte[] itsCodeAttribute; + +} + +final class ConstantPool +{ + + ConstantPool(ClassFileWriter cfw) + { + this.cfw = cfw; + itsTopIndex = 1; // the zero'th entry is reserved + itsPool = new byte[ConstantPoolSize]; + itsTop = 0; + } + + private static final int ConstantPoolSize = 256; + private static final byte + CONSTANT_Class = 7, + CONSTANT_Fieldref = 9, + CONSTANT_Methodref = 10, + CONSTANT_InterfaceMethodref = 11, + CONSTANT_String = 8, + CONSTANT_Integer = 3, + CONSTANT_Float = 4, + CONSTANT_Long = 5, + CONSTANT_Double = 6, + CONSTANT_NameAndType = 12, + CONSTANT_Utf8 = 1; + + int write(byte[] data, int offset) + { + offset = ClassFileWriter.putInt16((short)itsTopIndex, data, offset); + System.arraycopy(itsPool, 0, data, offset, itsTop); + offset += itsTop; + return offset; + } + + int getWriteSize() + { + return 2 + itsTop; + } + + int addConstant(int k) + { + ensure(5); + itsPool[itsTop++] = CONSTANT_Integer; + itsTop = ClassFileWriter.putInt32(k, itsPool, itsTop); + return (short)(itsTopIndex++); + } + + int addConstant(long k) + { + ensure(9); + itsPool[itsTop++] = CONSTANT_Long; + itsTop = ClassFileWriter.putInt64(k, itsPool, itsTop); + int index = itsTopIndex; + itsTopIndex += 2; + return index; + } + + int addConstant(float k) + { + ensure(5); + itsPool[itsTop++] = CONSTANT_Float; + int bits = Float.floatToIntBits(k); + itsTop = ClassFileWriter.putInt32(bits, itsPool, itsTop); + return itsTopIndex++; + } + + int addConstant(double k) + { + ensure(9); + itsPool[itsTop++] = CONSTANT_Double; + long bits = Double.doubleToLongBits(k); + itsTop = ClassFileWriter.putInt64(bits, itsPool, itsTop); + int index = itsTopIndex; + itsTopIndex += 2; + return index; + } + + int addConstant(String k) + { + int utf8Index = 0xFFFF & addUtf8(k); + int theIndex = itsStringConstHash.getInt(utf8Index, -1); + if (theIndex == -1) { + theIndex = itsTopIndex++; + ensure(3); + itsPool[itsTop++] = CONSTANT_String; + itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop); + itsStringConstHash.put(utf8Index, theIndex); + } + return theIndex; + } + + boolean isUnderUtfEncodingLimit(String s) + { + int strLen = s.length(); + if (strLen * 3 <= MAX_UTF_ENCODING_SIZE) { + return true; + } else if (strLen > MAX_UTF_ENCODING_SIZE) { + return false; + } + return strLen == getUtfEncodingLimit(s, 0, strLen); + } + + /** + * Get maximum i such that <tt>start <= i <= end</tt> and + * <tt>s.substring(start, i)</tt> fits JVM UTF string encoding limit. + */ + int getUtfEncodingLimit(String s, int start, int end) + { + if ((end - start) * 3 <= MAX_UTF_ENCODING_SIZE) { + return end; + } + int limit = MAX_UTF_ENCODING_SIZE; + for (int i = start; i != end; i++) { + int c = s.charAt(i); + if (0 != c && c <= 0x7F) { + --limit; + } else if (c < 0x7FF) { + limit -= 2; + } else { + limit -= 3; + } + if (limit < 0) { + return i; + } + } + return end; + } + + short addUtf8(String k) + { + int theIndex = itsUtf8Hash.get(k, -1); + if (theIndex == -1) { + int strLen = k.length(); + boolean tooBigString; + if (strLen > MAX_UTF_ENCODING_SIZE) { + tooBigString = true; + } else { + tooBigString = false; + // Ask for worst case scenario buffer when each char takes 3 + // bytes + ensure(1 + 2 + strLen * 3); + int top = itsTop; + + itsPool[top++] = CONSTANT_Utf8; + top += 2; // skip length + + char[] chars = cfw.getCharBuffer(strLen); + k.getChars(0, strLen, chars, 0); + + for (int i = 0; i != strLen; i++) { + int c = chars[i]; + if (c != 0 && c <= 0x7F) { + itsPool[top++] = (byte)c; + } else if (c > 0x7FF) { + itsPool[top++] = (byte)(0xE0 | (c >> 12)); + itsPool[top++] = (byte)(0x80 | ((c >> 6) & 0x3F)); + itsPool[top++] = (byte)(0x80 | (c & 0x3F)); + } else { + itsPool[top++] = (byte)(0xC0 | (c >> 6)); + itsPool[top++] = (byte)(0x80 | (c & 0x3F)); + } + } + + int utfLen = top - (itsTop + 1 + 2); + if (utfLen > MAX_UTF_ENCODING_SIZE) { + tooBigString = true; + } else { + // Write back length + itsPool[itsTop + 1] = (byte)(utfLen >>> 8); + itsPool[itsTop + 2] = (byte)utfLen; + + itsTop = top; + theIndex = itsTopIndex++; + itsUtf8Hash.put(k, theIndex); + } + } + if (tooBigString) { + throw new IllegalArgumentException("Too big string"); + } + } + return (short)theIndex; + } + + private short addNameAndType(String name, String type) + { + short nameIndex = addUtf8(name); + short typeIndex = addUtf8(type); + ensure(5); + itsPool[itsTop++] = CONSTANT_NameAndType; + itsTop = ClassFileWriter.putInt16(nameIndex, itsPool, itsTop); + itsTop = ClassFileWriter.putInt16(typeIndex, itsPool, itsTop); + return (short)(itsTopIndex++); + } + + short addClass(String className) + { + int theIndex = itsClassHash.get(className, -1); + if (theIndex == -1) { + String slashed = className; + if (className.indexOf('.') > 0) { + slashed = ClassFileWriter.getSlashedForm(className); + theIndex = itsClassHash.get(slashed, -1); + if (theIndex != -1) { + itsClassHash.put(className, theIndex); + } + } + if (theIndex == -1) { + int utf8Index = addUtf8(slashed); + ensure(3); + itsPool[itsTop++] = CONSTANT_Class; + itsTop = ClassFileWriter.putInt16(utf8Index, itsPool, itsTop); + theIndex = itsTopIndex++; + itsClassHash.put(slashed, theIndex); + if (className != slashed) { + itsClassHash.put(className, theIndex); + } + } + } + return (short)theIndex; + } + + short addFieldRef(String className, String fieldName, String fieldType) + { + FieldOrMethodRef ref = new FieldOrMethodRef(className, fieldName, + fieldType); + + int theIndex = itsFieldRefHash.get(ref, -1); + if (theIndex == -1) { + short ntIndex = addNameAndType(fieldName, fieldType); + short classIndex = addClass(className); + ensure(5); + itsPool[itsTop++] = CONSTANT_Fieldref; + itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop); + itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop); + theIndex = itsTopIndex++; + itsFieldRefHash.put(ref, theIndex); + } + return (short)theIndex; + } + + short addMethodRef(String className, String methodName, + String methodType) + { + FieldOrMethodRef ref = new FieldOrMethodRef(className, methodName, + methodType); + + int theIndex = itsMethodRefHash.get(ref, -1); + if (theIndex == -1) { + short ntIndex = addNameAndType(methodName, methodType); + short classIndex = addClass(className); + ensure(5); + itsPool[itsTop++] = CONSTANT_Methodref; + itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop); + itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop); + theIndex = itsTopIndex++; + itsMethodRefHash.put(ref, theIndex); + } + return (short)theIndex; + } + + short addInterfaceMethodRef(String className, + String methodName, String methodType) + { + short ntIndex = addNameAndType(methodName, methodType); + short classIndex = addClass(className); + ensure(5); + itsPool[itsTop++] = CONSTANT_InterfaceMethodref; + itsTop = ClassFileWriter.putInt16(classIndex, itsPool, itsTop); + itsTop = ClassFileWriter.putInt16(ntIndex, itsPool, itsTop); + return (short)(itsTopIndex++); + } + + void ensure(int howMuch) + { + if (itsTop + howMuch > itsPool.length) { + int newCapacity = itsPool.length * 2; + if (itsTop + howMuch > newCapacity) { + newCapacity = itsTop + howMuch; + } + byte[] tmp = new byte[newCapacity]; + System.arraycopy(itsPool, 0, tmp, 0, itsTop); + itsPool = tmp; + } + } + + private ClassFileWriter cfw; + + private static final int MAX_UTF_ENCODING_SIZE = 65535; + + private UintMap itsStringConstHash = new UintMap(); + private ObjToIntMap itsUtf8Hash = new ObjToIntMap(); + private ObjToIntMap itsFieldRefHash = new ObjToIntMap(); + private ObjToIntMap itsMethodRefHash = new ObjToIntMap(); + private ObjToIntMap itsClassHash = new ObjToIntMap(); + + private int itsTop; + private int itsTopIndex; + private byte itsPool[]; +} + +final class FieldOrMethodRef +{ + FieldOrMethodRef(String className, String name, String type) + { + this.className = className; + this.name = name; + this.type = type; + } + + public boolean equals(Object obj) + { + if (!(obj instanceof FieldOrMethodRef)) { return false; } + FieldOrMethodRef x = (FieldOrMethodRef)obj; + return className.equals(x.className) + && name.equals(x.name) + && type.equals(x.type); + } + + public int hashCode() + { + if (hashCode == -1) { + int h1 = className.hashCode(); + int h2 = name.hashCode(); + int h3 = type.hashCode(); + hashCode = h1 ^ h2 ^ h3; + } + return hashCode; + } + + private String className; + private String name; + private String type; + private int hashCode = -1; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Arguments.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Arguments.java new file mode 100644 index 0000000..954b078 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Arguments.java @@ -0,0 +1,311 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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; + +/** + * This class implements the "arguments" object. + * + * See ECMA 10.1.8 + * + * @see org.mozilla.javascript.NativeCall + * @author Norris Boyd + */ +final class Arguments extends IdScriptableObject +{ + static final long serialVersionUID = 4275508002492040609L; + + public Arguments(NativeCall activation) + { + this.activation = activation; + + Scriptable parent = activation.getParentScope(); + setParentScope(parent); + setPrototype(ScriptableObject.getObjectPrototype(parent)); + + args = activation.originalArgs; + lengthObj = new Integer(args.length); + + NativeFunction f = activation.function; + calleeObj = f; + + int version = f.getLanguageVersion(); + if (version <= Context.VERSION_1_3 + && version != Context.VERSION_DEFAULT) + { + callerObj = null; + } else { + callerObj = NOT_FOUND; + } + } + + public String getClassName() + { + return "Object"; + } + + public boolean has(int index, Scriptable start) + { + if (0 <= index && index < args.length) { + if (args[index] != NOT_FOUND) { + return true; + } + } + return super.has(index, start); + } + + public Object get(int index, Scriptable start) + { + if (0 <= index && index < args.length) { + Object value = args[index]; + if (value != NOT_FOUND) { + if (sharedWithActivation(index)) { + NativeFunction f = activation.function; + String argName = f.getParamOrVarName(index); + value = activation.get(argName, activation); + if (value == NOT_FOUND) Kit.codeBug(); + } + return value; + } + } + return super.get(index, start); + } + + private boolean sharedWithActivation(int index) + { + NativeFunction f = activation.function; + int definedCount = f.getParamCount(); + if (index < definedCount) { + // Check if argument is not hidden by later argument with the same + // name as hidden arguments are not shared with activation + if (index < definedCount - 1) { + String argName = f.getParamOrVarName(index); + for (int i = index + 1; i < definedCount; i++) { + if (argName.equals(f.getParamOrVarName(i))) { + return false; + } + } + } + return true; + } + return false; + } + + public void put(int index, Scriptable start, Object value) + { + if (0 <= index && index < args.length) { + if (args[index] != NOT_FOUND) { + if (sharedWithActivation(index)) { + String argName; + argName = activation.function.getParamOrVarName(index); + activation.put(argName, activation, value); + return; + } + synchronized (this) { + if (args[index] != NOT_FOUND) { + if (args == activation.originalArgs) { + args = args.clone(); + } + args[index] = value; + return; + } + } + } + } + super.put(index, start, value); + } + + public void delete(int index) + { + if (0 <= index && index < args.length) { + synchronized (this) { + if (args[index] != NOT_FOUND) { + if (args == activation.originalArgs) { + args = args.clone(); + } + args[index] = NOT_FOUND; + return; + } + } + } + super.delete(index); + } + +// #string_id_map# + + private static final int + Id_callee = 1, + Id_length = 2, + Id_caller = 3, + + MAX_INSTANCE_ID = 3; + + protected int getMaxInstanceId() + { + return MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:04 EDT + L0: { id = 0; String X = null; int c; + if (s.length()==6) { + c=s.charAt(5); + if (c=='e') { X="callee";id=Id_callee; } + else if (c=='h') { X="length";id=Id_length; } + else if (c=='r') { X="caller";id=Id_caller; } + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + + if (id == 0) return super.findInstanceIdInfo(s); + + int attr; + switch (id) { + case Id_callee: + case Id_caller: + case Id_length: + attr = DONTENUM; + break; + default: throw new IllegalStateException(); + } + return instanceIdInfo(attr, id); + } + +// #/string_id_map# + + protected String getInstanceIdName(int id) + { + switch (id) { + case Id_callee: return "callee"; + case Id_length: return "length"; + case Id_caller: return "caller"; + } + return null; + } + + protected Object getInstanceIdValue(int id) + { + switch (id) { + case Id_callee: return calleeObj; + case Id_length: return lengthObj; + case Id_caller: { + Object value = callerObj; + if (value == UniqueTag.NULL_VALUE) { value = null; } + else if (value == null) { + NativeCall caller = activation.parentActivationCall; + if (caller != null) { + value = caller.get("arguments", caller); + } + } + return value; + } + } + return super.getInstanceIdValue(id); + } + + protected void setInstanceIdValue(int id, Object value) + { + switch (id) { + case Id_callee: calleeObj = value; return; + case Id_length: lengthObj = value; return; + case Id_caller: + callerObj = (value != null) ? value : UniqueTag.NULL_VALUE; + return; + } + super.setInstanceIdValue(id, value); + } + + Object[] getIds(boolean getAll) + { + Object[] ids = super.getIds(getAll); + if (getAll && args.length != 0) { + boolean[] present = null; + int extraCount = args.length; + for (int i = 0; i != ids.length; ++i) { + Object id = ids[i]; + if (id instanceof Integer) { + int index = ((Integer)id).intValue(); + if (0 <= index && index < args.length) { + if (present == null) { + present = new boolean[args.length]; + } + if (!present[index]) { + present[index] = true; + extraCount--; + } + } + } + } + if (extraCount != 0) { + Object[] tmp = new Object[extraCount + ids.length]; + System.arraycopy(ids, 0, tmp, extraCount, ids.length); + ids = tmp; + int offset = 0; + for (int i = 0; i != args.length; ++i) { + if (present == null || !present[i]) { + ids[offset] = new Integer(i); + ++offset; + } + } + if (offset != extraCount) Kit.codeBug(); + } + } + return ids; + } + +// Fields to hold caller, callee and length properties, +// where NOT_FOUND value tags deleted properties. +// In addition if callerObj == NULL_VALUE, it tags null for scripts, as +// initial callerObj == null means access to caller arguments available +// only in JS <= 1.3 scripts + private Object callerObj; + private Object calleeObj; + private Object lengthObj; + + private NativeCall activation; + +// Initially args holds activation.getOriginalArgs(), but any modification +// of its elements triggers creation of a copy. If its element holds NOT_FOUND, +// it indicates deleted index, in which case super class is queried. + private Object[] args; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/BaseFunction.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/BaseFunction.java new file mode 100644 index 0000000..d7d8992 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/BaseFunction.java @@ -0,0 +1,553 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * Mike McCabe + * + * 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; + +/** + * The base class for Function objects + * See ECMA 15.3. + * @author Norris Boyd + */ +public class BaseFunction extends IdScriptableObject implements Function +{ + + static final long serialVersionUID = 5311394446546053859L; + + private static final Object FUNCTION_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + BaseFunction obj = new BaseFunction(); + // Function.prototype attributes: see ECMA 15.3.3.1 + obj.prototypePropertyAttributes = DONTENUM | READONLY | PERMANENT; + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + public BaseFunction() + { + } + + public BaseFunction(Scriptable scope, Scriptable prototype) + { + super(scope, prototype); + } + + public String getClassName() { + return "Function"; + } + + /** + * Implements the instanceof operator for JavaScript Function objects. + * <p> + * <code> + * foo = new Foo();<br> + * foo instanceof Foo; // true<br> + * </code> + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * @return true if the "prototype" property of "this" appears in + * value's prototype chain + * + */ + public boolean hasInstance(Scriptable instance) + { + Object protoProp = ScriptableObject.getProperty(this, "prototype"); + if (protoProp instanceof Scriptable) { + return ScriptRuntime.jsDelegatesTo(instance, (Scriptable)protoProp); + } + throw ScriptRuntime.typeError1("msg.instanceof.bad.prototype", + getFunctionName()); + } + +// #string_id_map# + + private static final int + Id_length = 1, + Id_arity = 2, + Id_name = 3, + Id_prototype = 4, + Id_arguments = 5, + + MAX_INSTANCE_ID = 5; + + protected int getMaxInstanceId() + { + return MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:15 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 4: X="name";id=Id_name; break L; + case 5: X="arity";id=Id_arity; break L; + case 6: X="length";id=Id_length; break L; + case 9: c=s.charAt(0); + if (c=='a') { X="arguments";id=Id_arguments; } + else if (c=='p') { X="prototype";id=Id_prototype; } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# +// #/string_id_map# + + if (id == 0) return super.findInstanceIdInfo(s); + + int attr; + switch (id) { + case Id_length: + case Id_arity: + case Id_name: + attr = DONTENUM | READONLY | PERMANENT; + break; + case Id_prototype: + attr = prototypePropertyAttributes; + break; + case Id_arguments: + attr = DONTENUM | PERMANENT; + break; + default: throw new IllegalStateException(); + } + return instanceIdInfo(attr, id); + } + + protected String getInstanceIdName(int id) + { + switch (id) { + case Id_length: return "length"; + case Id_arity: return "arity"; + case Id_name: return "name"; + case Id_prototype: return "prototype"; + case Id_arguments: return "arguments"; + } + return super.getInstanceIdName(id); + } + + protected Object getInstanceIdValue(int id) + { + switch (id) { + case Id_length: return ScriptRuntime.wrapInt(getLength()); + case Id_arity: return ScriptRuntime.wrapInt(getArity()); + case Id_name: return getFunctionName(); + case Id_prototype: return getPrototypeProperty(); + case Id_arguments: return getArguments(); + } + return super.getInstanceIdValue(id); + } + + protected void setInstanceIdValue(int id, Object value) + { + if (id == Id_prototype) { + if ((prototypePropertyAttributes & READONLY) == 0) { + prototypeProperty = (value != null) + ? value : UniqueTag.NULL_VALUE; + } + return; + } else if (id == Id_arguments) { + if (value == NOT_FOUND) { + // This should not be called since "arguments" is PERMANENT + Kit.codeBug(); + } + defaultPut("arguments", value); + } + super.setInstanceIdValue(id, value); + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + // Fix up bootstrapping problem: getPrototype of the IdFunctionObject + // can not return Function.prototype because Function object is not + // yet defined. + ctor.setPrototype(this); + super.fillConstructorProperties(ctor); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=1; s="toString"; break; + case Id_toSource: arity=1; s="toSource"; break; + case Id_apply: arity=2; s="apply"; break; + case Id_call: arity=1; s="call"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(FUNCTION_TAG, id, s, arity); + } + + static boolean isApply(IdFunctionObject f) { + return f.hasTag(FUNCTION_TAG) && f.methodId() == Id_apply; + } + + static boolean isApplyOrCall(IdFunctionObject f) { + if(f.hasTag(FUNCTION_TAG)) { + switch(f.methodId()) { + case Id_apply: + case Id_call: + return true; + } + } + return false; + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(FUNCTION_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: + return jsConstructor(cx, scope, args); + + case Id_toString: { + BaseFunction realf = realFunction(thisObj, f); + int indent = ScriptRuntime.toInt32(args, 0); + return realf.decompile(indent, 0); + } + + case Id_toSource: { + BaseFunction realf = realFunction(thisObj, f); + int indent = 0; + int flags = Decompiler.TO_SOURCE_FLAG; + if (args.length != 0) { + indent = ScriptRuntime.toInt32(args[0]); + if (indent >= 0) { + flags = 0; + } else { + indent = 0; + } + } + return realf.decompile(indent, flags); + } + + case Id_apply: + case Id_call: + return ScriptRuntime.applyOrCall(id == Id_apply, + cx, scope, thisObj, args); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private BaseFunction realFunction(Scriptable thisObj, IdFunctionObject f) + { + Object x = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); + if (x instanceof BaseFunction) { + return (BaseFunction)x; + } + throw ScriptRuntime.typeError1("msg.incompat.call", + f.getFunctionName()); + } + + /** + * Make value as DontEnum, DontDelete, ReadOnly + * prototype property of this Function object + */ + public void setImmunePrototypeProperty(Object value) + { + if ((prototypePropertyAttributes & READONLY) != 0) { + throw new IllegalStateException(); + } + prototypeProperty = (value != null) ? value : UniqueTag.NULL_VALUE; + prototypePropertyAttributes = DONTENUM | PERMANENT | READONLY; + } + + protected Scriptable getClassPrototype() + { + Object protoVal = getPrototypeProperty(); + if (protoVal instanceof Scriptable) { + return (Scriptable) protoVal; + } + return getClassPrototype(this, "Object"); + } + + /** + * Should be overridden. + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return Undefined.instance; + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + Scriptable result = createObject(cx, scope); + if (result != null) { + Object val = call(cx, scope, result, args); + if (val instanceof Scriptable) { + result = (Scriptable)val; + } + } else { + Object val = call(cx, scope, null, args); + if (!(val instanceof Scriptable)) { + // It is program error not to return Scriptable from + // the call method if createObject returns null. + throw new IllegalStateException( + "Bad implementaion of call as constructor, name=" + +getFunctionName()+" in "+getClass().getName()); + } + result = (Scriptable)val; + if (result.getPrototype() == null) { + result.setPrototype(getClassPrototype()); + } + if (result.getParentScope() == null) { + Scriptable parent = getParentScope(); + if (result != parent) { + result.setParentScope(parent); + } + } + } + return result; + } + + /** + * Creates new script object. + * The default implementation of {@link #construct} uses the method to + * to get the value for <tt>thisObj</tt> argument when invoking + * {@link #call}. + * The methos is allowed to return <tt>null</tt> to indicate that + * {@link #call} will create a new object itself. In this case + * {@link #construct} will set scope and prototype on the result + * {@link #call} unless they are already set. + */ + public Scriptable createObject(Context cx, Scriptable scope) + { + Scriptable newInstance = new NativeObject(); + newInstance.setPrototype(getClassPrototype()); + newInstance.setParentScope(getParentScope()); + return newInstance; + } + + /** + * Decompile the source information associated with this js + * function/script back into a string. + * + * @param indent How much to indent the decompiled result. + * + * @param flags Flags specifying format of decompilation output. + */ + String decompile(int indent, int flags) + { + StringBuffer sb = new StringBuffer(); + boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() {\n\t"); + } + sb.append("[native code, arity="); + sb.append(getArity()); + sb.append("]\n"); + if (!justbody) { + sb.append("}\n"); + } + return sb.toString(); + } + + public int getArity() { return 0; } + + public int getLength() { return 0; } + + public String getFunctionName() + { + return ""; + } + + final Object getPrototypeProperty() { + Object result = prototypeProperty; + if (result == null) { + synchronized (this) { + result = prototypeProperty; + if (result == null) { + setupDefaultPrototype(); + result = prototypeProperty; + } + } + } + else if (result == UniqueTag.NULL_VALUE) { result = null; } + return result; + } + + private void setupDefaultPrototype() + { + NativeObject obj = new NativeObject(); + final int attr = ScriptableObject.DONTENUM; + obj.defineProperty("constructor", this, attr); + // put the prototype property into the object now, then in the + // wacky case of a user defining a function Object(), we don't + // get an infinite loop trying to find the prototype. + prototypeProperty = obj; + Scriptable proto = getObjectPrototype(this); + if (proto != obj) { + // not the one we just made, it must remain grounded + obj.setPrototype(proto); + } + } + + private Object getArguments() + { + // <Function name>.arguments is deprecated, so we use a slow + // way of getting it that doesn't add to the invocation cost. + // TODO: add warning, error based on version + Object value = defaultGet("arguments"); + if (value != NOT_FOUND) { + // Should after changing <Function name>.arguments its + // activation still be available during Function call? + // This code assumes it should not: + // defaultGet("arguments") != NOT_FOUND + // means assigned arguments + return value; + } + Context cx = Context.getContext(); + NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this); + return (activation == null) + ? null + : activation.get("arguments", activation); + } + + private static Object jsConstructor(Context cx, Scriptable scope, + Object[] args) + { + int arglen = args.length; + StringBuffer sourceBuf = new StringBuffer(); + + sourceBuf.append("function "); + /* version != 1.2 Function constructor behavior - + * print 'anonymous' as the function name if the + * version (under which the function was compiled) is + * less than 1.2... or if it's greater than 1.2, because + * we need to be closer to ECMA. + */ + if (cx.getLanguageVersion() != Context.VERSION_1_2) { + sourceBuf.append("anonymous"); + } + sourceBuf.append('('); + + // Append arguments as coma separated strings + for (int i = 0; i < arglen - 1; i++) { + if (i > 0) { + sourceBuf.append(','); + } + sourceBuf.append(ScriptRuntime.toString(args[i])); + } + sourceBuf.append(") {"); + if (arglen != 0) { + // append function body + String funBody = ScriptRuntime.toString(args[arglen - 1]); + sourceBuf.append(funBody); + } + sourceBuf.append('}'); + String source = sourceBuf.toString(); + + int[] linep = new int[1]; + String filename = Context.getSourcePositionFromStack(linep); + if (filename == null) { + filename = "<eval'ed string>"; + linep[0] = 1; + } + + String sourceURI = ScriptRuntime. + makeUrlForGeneratedScript(false, filename, linep[0]); + + Scriptable global = ScriptableObject.getTopLevelScope(scope); + + ErrorReporter reporter; + reporter = DefaultErrorReporter.forEval(cx.getErrorReporter()); + + Evaluator evaluator = Context.createInterpreter(); + if (evaluator == null) { + throw new JavaScriptException("Interpreter not present", + filename, linep[0]); + } + + // Compile with explicit interpreter instance to force interpreter + // mode. + return cx.compileFunction(global, source, evaluator, reporter, + sourceURI, 1, null); + } + + protected int findPrototypeId(String s) + { + int id; +// #string_id_map# +// #generated# Last update: 2007-05-09 08:15:15 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 4: X="call";id=Id_call; break L; + case 5: X="apply";id=Id_apply; break L; + case 8: c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + case 11: X="constructor";id=Id_constructor; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toSource = 3, + Id_apply = 4, + Id_call = 5, + + MAX_PROTOTYPE_ID = 5; + +// #/string_id_map# + + private Object prototypeProperty; + // For function object instances, attribute is PERMANENT; see ECMA 15.3.5.2 + private int prototypePropertyAttributes = PERMANENT; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Callable.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Callable.java new file mode 100644 index 0000000..03e0fce --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Callable.java @@ -0,0 +1,59 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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; + +/** + * Generic notion of callable object that can execute some script-related code + * upon request with specified values for script scope and this objects. + */ +public interface Callable +{ + /** + * 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); +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassCache.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassCache.java new file mode 100644 index 0000000..9047278 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassCache.java @@ -0,0 +1,220 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * Norris Boyd + * + * 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.util.Map; +import java.util.HashMap; + +/** + * Cache of generated classes and data structures to access Java runtime + * from JavaScript. + * + * @author Igor Bukanov + * + * @since Rhino 1.5 Release 5 + */ +public class ClassCache +{ + private static final Object AKEY = new Object(); + private volatile boolean cachingIsEnabled = true; + private HashMap<Class<?>,JavaMembers> classTable + = new HashMap<Class<?>,JavaMembers>(); + private HashMap<Class<?>,JavaMembers> javaAdapterGeneratedClasses + = new HashMap<Class<?>,JavaMembers>(); + private HashMap<JavaAdapter.JavaAdapterSignature,Class<?>> classAdapterCache + = new HashMap<JavaAdapter.JavaAdapterSignature,Class<?>>(); + private HashMap<Class<?>,Object> interfaceAdapterCache; + private int generatedClassSerial; + + /** + * Search for ClassCache object in the given scope. + * The method first calls + * {@link ScriptableObject#getTopLevelScope(Scriptable scope)} + * to get the top most scope and then tries to locate associated + * ClassCache object in the prototype chain of the top scope. + * + * @param scope scope to search for ClassCache object. + * @return previously associated ClassCache object or a new instance of + * ClassCache if no ClassCache object was found. + * + * @see #associate(ScriptableObject topScope) + */ + public static ClassCache get(Scriptable scope) + { + ClassCache cache = (ClassCache) + ScriptableObject.getTopScopeValue(scope, AKEY); + if (cache == null) { + throw new RuntimeException("Can't find top level scope for " + + "ClassCache.get"); + } + return cache; + } + + /** + * Associate ClassCache object with the given top-level scope. + * The ClassCache object can only be associated with the given scope once. + * + * @param topScope scope to associate this ClassCache object with. + * @return true if no previous ClassCache objects were embedded into + * the scope and this ClassCache were successfully associated + * or false otherwise. + * + * @see #get(Scriptable scope) + */ + public boolean associate(ScriptableObject topScope) + { + if (topScope.getParentScope() != null) { + // Can only associate cache with top level scope + throw new IllegalArgumentException(); + } + if (this == topScope.associateValue(AKEY, this)) { + return true; + } + return false; + } + + /** + * Empty caches of generated Java classes and Java reflection information. + */ + public synchronized void clearCaches() + { + classTable.clear(); + javaAdapterGeneratedClasses.clear(); + classAdapterCache.clear(); + interfaceAdapterCache = null; + } + + /** + * Check if generated Java classes and Java reflection information + * is cached. + */ + public final boolean isCachingEnabled() + { + return cachingIsEnabled; + } + + /** + * Set whether to cache some values. + * <p> + * By default, the engine will cache the results of + * <tt>Class.getMethods()</tt> and similar calls. + * This can speed execution dramatically, but increases the memory + * footprint. Also, with caching enabled, references may be held to + * objects past the lifetime of any real usage. + * <p> + * If caching is enabled and this method is called with a + * <code>false</code> argument, the caches will be emptied. + * <p> + * Caching is enabled by default. + * + * @param enabled if true, caching is enabled + * + * @see #clearCaches() + */ + public synchronized void setCachingEnabled(boolean enabled) + { + if (enabled == cachingIsEnabled) + return; + if (!enabled) + clearCaches(); + cachingIsEnabled = enabled; + } + + /** + * @return a map from classes to associated JavaMembers objects + */ + Map<Class<?>,JavaMembers> getClassCacheMap() { + return classTable; + } + + Map<JavaAdapter.JavaAdapterSignature,Class<?>> getInterfaceAdapterCacheMap() + { + return classAdapterCache; + } + + /** + * @deprecated + * The method always returns false. + * @see #setInvokerOptimizationEnabled(boolean enabled) + */ + public boolean isInvokerOptimizationEnabled() + { + return false; + } + + /** + * @deprecated + * The method does nothing. + * Invoker optimization is no longer used by Rhino. + * On modern JDK like 1.4 or 1.5 the disadvantages of the optimization + * like increased memory usage or longer initialization time overweight + * small speed increase that can be gained using generated proxy class + * to replace reflection. + */ + public synchronized void setInvokerOptimizationEnabled(boolean enabled) + { + } + + /** + * Internal engine method to return serial number for generated classes + * to ensure name uniqueness. + */ + public final synchronized int newClassSerialNumber() + { + return ++generatedClassSerial; + } + + Object getInterfaceAdapter(Class<?> cl) + { + return interfaceAdapterCache == null + ? null + : interfaceAdapterCache.get(cl); + } + + synchronized void cacheInterfaceAdapter(Class<?> cl, Object iadapter) + { + if (cachingIsEnabled) { + if (interfaceAdapterCache == null) { + interfaceAdapterCache = new HashMap<Class<?>,Object>(); + } + interfaceAdapterCache.put(cl, iadapter); + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassShutter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassShutter.java new file mode 100644 index 0000000..d5f4cd6 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ClassShutter.java @@ -0,0 +1,89 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** +Embeddings that wish to filter Java classes that are visible to scripts +through the LiveConnect, should implement this interface. + +@see Context#setClassShutter(ClassShutter) +@since 1.5 Release 4 +@author Norris Boyd +*/ + + public interface ClassShutter { + + /** + * Return true iff the Java class with the given name should be exposed + * to scripts. + * <p> + * An embedding may filter which Java classes are exposed through + * LiveConnect to JavaScript scripts. + * <p> + * Due to the fact that there is no package reflection in Java, + * this method will also be called with package names. There + * is no way for Rhino to tell if "Packages.a.b" is a package name + * or a class that doesn't exist. What Rhino does is attempt + * to load each segment of "Packages.a.b.c": It first attempts to + * load class "a", then attempts to load class "a.b", then + * finally attempts to load class "a.b.c". On a Rhino installation + * without any ClassShutter set, and without any of the + * above classes, the expression "Packages.a.b.c" will result in + * a [JavaPackage a.b.c] and not an error. + * <p> + * With ClassShutter supplied, Rhino will first call + * visibleToScripts before attempting to look up the class name. If + * visibleToScripts returns false, the class name lookup is not + * performed and subsequent Rhino execution assumes the class is + * not present. So for "java.lang.System.out.println" the lookup + * of "java.lang.System" is skipped and thus Rhino assumes that + * "java.lang.System" doesn't exist. So then for "java.lang.System.out", + * Rhino attempts to load the class "java.lang.System.out" because + * it assumes that "java.lang.System" is a package name. + * <p> + * @param fullClassName the full name of the class (including the package + * name, with '.' as a delimiter). For example the + * standard string class is "java.lang.String" + * @return whether or not to reveal this class to scripts + */ + public boolean visibleToScripts(String fullClassName); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/CompilerEnvirons.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/CompilerEnvirons.java new file mode 100644 index 0000000..645d098 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/CompilerEnvirons.java @@ -0,0 +1,233 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * Bob Jervis + * + * 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.util.Hashtable; + +public class CompilerEnvirons +{ + public CompilerEnvirons() + { + errorReporter = DefaultErrorReporter.instance; + languageVersion = Context.VERSION_DEFAULT; + generateDebugInfo = true; + useDynamicScope = false; + reservedKeywordAsIdentifier = false; + allowMemberExprAsFunctionName = false; + xmlAvailable = true; + optimizationLevel = 0; + generatingSource = true; + strictMode = false; + warningAsError = false; + generateObserverCount = false; + } + + public void initFromContext(Context cx) + { + setErrorReporter(cx.getErrorReporter()); + this.languageVersion = cx.getLanguageVersion(); + useDynamicScope = cx.compileFunctionsWithDynamicScopeFlag; + generateDebugInfo = (!cx.isGeneratingDebugChanged() + || cx.isGeneratingDebug()); + reservedKeywordAsIdentifier + = cx.hasFeature(Context.FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER); + allowMemberExprAsFunctionName + = cx.hasFeature(Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME); + strictMode + = cx.hasFeature(Context.FEATURE_STRICT_MODE); + warningAsError = cx.hasFeature(Context.FEATURE_WARNING_AS_ERROR); + xmlAvailable + = cx.hasFeature(Context.FEATURE_E4X); + + optimizationLevel = cx.getOptimizationLevel(); + + generatingSource = cx.isGeneratingSource(); + activationNames = cx.activationNames; + + // Observer code generation in compiled code : + generateObserverCount = cx.generateObserverCount; + } + + public final ErrorReporter getErrorReporter() + { + return errorReporter; + } + + public void setErrorReporter(ErrorReporter errorReporter) + { + if (errorReporter == null) throw new IllegalArgumentException(); + this.errorReporter = errorReporter; + } + + public final int getLanguageVersion() + { + return languageVersion; + } + + public void setLanguageVersion(int languageVersion) + { + Context.checkLanguageVersion(languageVersion); + this.languageVersion = languageVersion; + } + + public final boolean isGenerateDebugInfo() + { + return generateDebugInfo; + } + + public void setGenerateDebugInfo(boolean flag) + { + this.generateDebugInfo = flag; + } + + public final boolean isUseDynamicScope() + { + return useDynamicScope; + } + + public final boolean isReservedKeywordAsIdentifier() + { + return reservedKeywordAsIdentifier; + } + + public void setReservedKeywordAsIdentifier(boolean flag) + { + reservedKeywordAsIdentifier = flag; + } + + public final boolean isAllowMemberExprAsFunctionName() + { + return allowMemberExprAsFunctionName; + } + + public void setAllowMemberExprAsFunctionName(boolean flag) + { + allowMemberExprAsFunctionName = flag; + } + + public final boolean isXmlAvailable() + { + return xmlAvailable; + } + + public void setXmlAvailable(boolean flag) + { + xmlAvailable = flag; + } + + public final int getOptimizationLevel() + { + return optimizationLevel; + } + + public void setOptimizationLevel(int level) + { + Context.checkOptimizationLevel(level); + this.optimizationLevel = level; + } + + public final boolean isGeneratingSource() + { + return generatingSource; + } + + public final boolean isStrictMode() + { + return strictMode; + } + + public final boolean reportWarningAsError() + { + return warningAsError; + } + + /** + * Specify whether or not source information should be generated. + * <p> + * Without source information, evaluating the "toString" method + * on JavaScript functions produces only "[native code]" for + * the body of the function. + * Note that code generated without source is not fully ECMA + * conformant. + */ + public void setGeneratingSource(boolean generatingSource) + { + this.generatingSource = generatingSource; + } + + /** + * @return true iff code will be generated with callbacks to enable + * instruction thresholds + */ + public boolean isGenerateObserverCount() { + return generateObserverCount; + } + + /** + * Turn on or off generation of code with callbacks to + * track the count of executed instructions. + * Currently only affects JVM byte code generation: this slows down the + * generated code, but code generated without the callbacks will not + * be counted toward instruction thresholds. Rhino's interpretive + * mode does instruction counting without inserting callbacks, so + * there is no requirement to compile code differently. + * @param generateObserverCount if true, generated code will contain + * calls to accumulate an estimate of the instructions executed. + */ + public void setGenerateObserverCount(boolean generateObserverCount) { + this.generateObserverCount = generateObserverCount; + } + + private ErrorReporter errorReporter; + + private int languageVersion; + private boolean generateDebugInfo; + private boolean useDynamicScope; + private boolean reservedKeywordAsIdentifier; + private boolean allowMemberExprAsFunctionName; + private boolean xmlAvailable; + private int optimizationLevel; + private boolean generatingSource; + private boolean strictMode; + private boolean warningAsError; + private boolean generateObserverCount; + Hashtable activationNames; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ConstProperties.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ConstProperties.java new file mode 100644 index 0000000..860db79 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ConstProperties.java @@ -0,0 +1,109 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Bob Jervis + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +public interface ConstProperties { + /** + * Sets a named const property in this object. + * <p> + * The property is specified by a string name + * as defined for <code>Scriptable.get</code>. + * <p> + * The possible values that may be passed in are as defined for + * <code>Scriptable.get</code>. A class that implements this method may choose + * to ignore calls to set certain properties, in which case those + * properties are effectively read-only.<p> + * For properties defined in a prototype chain, + * use <code>putProperty</code> in ScriptableObject. <p> + * Note that if a property <i>a</i> is defined in the prototype <i>p</i> + * of an object <i>o</i>, then evaluating <code>o.a = 23</code> will cause + * <code>set</code> to be called on the prototype <i>p</i> with + * <i>o</i> as the <i>start</i> parameter. + * To preserve JavaScript semantics, it is the Scriptable + * object's responsibility to modify <i>o</i>. <p> + * This design allows properties to be defined in prototypes and implemented + * in terms of getters and setters of Java values without consuming slots + * in each instance.<p> + * <p> + * The values that may be set are limited to the following: + * <UL> + * <LI>java.lang.Boolean objects</LI> + * <LI>java.lang.String objects</LI> + * <LI>java.lang.Number objects</LI> + * <LI>org.mozilla.javascript.Scriptable objects</LI> + * <LI>null</LI> + * <LI>The value returned by Context.getUndefinedValue()</LI> + * </UL><p> + * Arbitrary Java objects may be wrapped in a Scriptable by first calling + * <code>Context.toObject</code>. This allows the property of a JavaScript + * object to contain an arbitrary Java object as a value.<p> + * Note that <code>has</code> will be called by the runtime first before + * <code>set</code> is called to determine in which object the + * property is defined. + * Note that this method is not expected to traverse the prototype chain, + * which is different from the ECMA [[Put]] operation. + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void putConst(String name, Scriptable start, Object value); + + /** + * Reserves a definition spot for a const. This will set up a definition + * of the const property, but set its value to undefined. The semantics of + * the start parameter is the same as for putConst. + * @param name The name of the property. + * @param start The object whose property is being reserved. + */ + public void defineConst(String name, Scriptable start); + + /** + * Returns true if the named property is defined as a const on this object. + * @param name + * @return true if the named property is defined as a const, false + * otherwise. + */ + public boolean isConst(String name); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Context.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Context.java new file mode 100644 index 0000000..0833883 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Context.java @@ -0,0 +1,2526 @@ +/* -*- 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): + * Bob Jervis + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Hashtable; +import java.util.Locale; + +import org.mozilla.javascript.debug.DebuggableScript; +import org.mozilla.javascript.debug.Debugger; +import org.mozilla.javascript.xml.XMLLib; + +/** + * This class represents the runtime context of an executing script. + * + * Before executing a script, an instance of Context must be created + * and associated with the thread that will be executing the script. + * The Context will be used to store information about the executing + * of the script such as the call stack. Contexts are associated with + * the current thread using the {@link #call(ContextAction)} + * or {@link #enter()} methods.<p> + * + * Different forms of script execution are supported. Scripts may be + * evaluated from the source directly, or first compiled and then later + * executed. Interactive execution is also supported.<p> + * + * Some aspects of script execution, such as type conversions and + * object creation, may be accessed directly through methods of + * Context. + * + * @see Scriptable + * @author Norris Boyd + * @author Brendan Eich + */ + +public class Context +{ + /** + * Language versions. + * + * All integral values are reserved for future version numbers. + */ + + /** + * The unknown version. + */ + public static final int VERSION_UNKNOWN = -1; + + /** + * The default version. + */ + public static final int VERSION_DEFAULT = 0; + + /** + * JavaScript 1.0 + */ + public static final int VERSION_1_0 = 100; + + /** + * JavaScript 1.1 + */ + public static final int VERSION_1_1 = 110; + + /** + * JavaScript 1.2 + */ + public static final int VERSION_1_2 = 120; + + /** + * JavaScript 1.3 + */ + public static final int VERSION_1_3 = 130; + + /** + * JavaScript 1.4 + */ + public static final int VERSION_1_4 = 140; + + /** + * JavaScript 1.5 + */ + public static final int VERSION_1_5 = 150; + + /** + * JavaScript 1.6 + */ + public static final int VERSION_1_6 = 160; + + /** + * JavaScript 1.7 + */ + public static final int VERSION_1_7 = 170; + + /** + * Controls behaviour of <tt>Date.prototype.getYear()</tt>. + * If <tt>hasFeature(FEATURE_NON_ECMA_GET_YEAR)</tt> returns true, + * Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000. + * The default behavior of {@link #hasFeature(int)} is always to subtruct + * 1900 as rquired by ECMAScript B.2.4. + */ + public static final int FEATURE_NON_ECMA_GET_YEAR = 1; + + /** + * Control if member expression as function name extension is available. + * If <tt>hasFeature(FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME)</tt> returns + * true, allow <tt>function memberExpression(args) { body }</tt> to be + * syntax sugar for <tt>memberExpression = function(args) { body }</tt>, + * when memberExpression is not a simple identifier. + * See ECMAScript-262, section 11.2 for definition of memberExpression. + * By default {@link #hasFeature(int)} returns false. + */ + public static final int FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME = 2; + + /** + * Control if reserved keywords are treated as identifiers. + * If <tt>hasFeature(RESERVED_KEYWORD_AS_IDENTIFIER)</tt> returns true, + * treat future reserved keyword (see Ecma-262, section 7.5.3) as ordinary + * identifiers but warn about this usage. + * + * By default {@link #hasFeature(int)} returns false. + */ + public static final int FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER = 3; + + /** + * Control if <tt>toString()</tt> should returns the same result + * as <tt>toSource()</tt> when applied to objects and arrays. + * If <tt>hasFeature(FEATURE_TO_STRING_AS_SOURCE)</tt> returns true, + * calling <tt>toString()</tt> on JS objects gives the same result as + * calling <tt>toSource()</tt>. That is it returns JS source with code + * to create an object with all enumeratable fields of the original object + * instead of printing <tt>[object <i>result of + * {@link Scriptable#getClassName()}</i>]</tt>. + * <p> + * By default {@link #hasFeature(int)} returns true only if + * the current JS version is set to {@link #VERSION_1_2}. + */ + public static final int FEATURE_TO_STRING_AS_SOURCE = 4; + + /** + * Control if properties <tt>__proto__</tt> and <tt>__parent__</tt> + * are treated specially. + * If <tt>hasFeature(FEATURE_PARENT_PROTO_PROPERTIES)</tt> returns true, + * treat <tt>__parent__</tt> and <tt>__proto__</tt> as special properties. + * <p> + * The properties allow to query and set scope and prototype chains for the + * objects. The special meaning of the properties is available + * only when they are used as the right hand side of the dot operator. + * For example, while <tt>x.__proto__ = y</tt> changes the prototype + * chain of the object <tt>x</tt> to point to <tt>y</tt>, + * <tt>x["__proto__"] = y</tt> simply assigns a new value to the property + * <tt>__proto__</tt> in <tt>x</tt> even when the feature is on. + * + * By default {@link #hasFeature(int)} returns true. + */ + public static final int FEATURE_PARENT_PROTO_PROPERTIES = 5; + + /** + * @deprecated In previous releases, this name was given to + * FEATURE_PARENT_PROTO_PROPERTIES. + */ + public static final int FEATURE_PARENT_PROTO_PROPRTIES = 5; + + /** + * Control if support for E4X(ECMAScript for XML) extension is available. + * If hasFeature(FEATURE_E4X) returns true, the XML syntax is available. + * <p> + * By default {@link #hasFeature(int)} returns true if + * the current JS version is set to {@link #VERSION_DEFAULT} + * or is at least {@link #VERSION_1_6}. + * @since 1.6 Release 1 + */ + public static final int FEATURE_E4X = 6; + + /** + * Control if dynamic scope should be used for name access. + * If hasFeature(FEATURE_DYNAMIC_SCOPE) returns true, then the name lookup + * during name resolution will use the top scope of the script or function + * which is at the top of JS execution stack instead of the top scope of the + * script or function from the current stack frame if the top scope of + * the top stack frame contains the top scope of the current stack frame + * on its prototype chain. + * <p> + * This is useful to define shared scope containing functions that can + * be called from scripts and functions using private scopes. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 1 + */ + public static final int FEATURE_DYNAMIC_SCOPE = 7; + + /** + * Control if strict variable mode is enabled. + * When the feature is on Rhino reports runtime errors if assignment + * to a global variable that does not exist is executed. When the feature + * is off such assignments creates new variable in the global scope as + * required by ECMA 262. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 1 + */ + public static final int FEATURE_STRICT_VARS = 8; + + /** + * Control if strict eval mode is enabled. + * When the feature is on Rhino reports runtime errors if non-string + * argument is passed to the eval function. When the feature is off + * eval simply return non-string argument as is without performing any + * evaluation as required by ECMA 262. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 1 + */ + public static final int FEATURE_STRICT_EVAL = 9; + + /** + * When the feature is on Rhino will add a "fileName" and "lineNumber" + * properties to Error objects automatically. When the feature is off, you + * have to explicitly pass them as the second and third argument to the + * Error constructor. Note that neither behaviour is fully ECMA 262 + * compliant (as 262 doesn't specify a three-arg constructor), but keeping + * the feature off results in Error objects that don't have + * additional non-ECMA properties when constructed using the ECMA-defined + * single-arg constructor and is thus desirable if a stricter ECMA + * compliance is desired, specifically adherence to the point 15.11.5. of + * the standard. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 6 + */ + public static final int FEATURE_LOCATION_INFORMATION_IN_ERROR = 10; + + /** + * Controls whether JS 1.5 'strict mode' is enabled. + * When the feature is on, Rhino reports more than a dozen different + * warnings. When the feature is off, these warnings are not generated. + * FEATURE_STRICT_MODE implies FEATURE_STRICT_VARS and FEATURE_STRICT_EVAL. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.6 Release 6 + */ + public static final int FEATURE_STRICT_MODE = 11; + + /** + * Controls whether a warning should be treated as an error. + * @since 1.6 Release 6 + */ + public static final int FEATURE_WARNING_AS_ERROR = 12; + + /** + * Enables enhanced access to Java. + * Specifically, controls whether private and protected members can be + * accessed, and whether scripts can catch all Java exceptions. + * <p> + * Note that this feature should only be enabled for trusted scripts. + * <p> + * By default {@link #hasFeature(int)} returns false. + * @since 1.7 Release 1 + */ + public static final int FEATURE_ENHANCED_JAVA_ACCESS = 13; + + + public static final String languageVersionProperty = "language version"; + public static final String errorReporterProperty = "error reporter"; + + /** + * Convenient value to use as zero-length array of objects. + */ + public static final Object[] emptyArgs = ScriptRuntime.emptyArgs; + + /** + * Create a new Context. + * + * Note that the Context must be associated with a thread before + * it can be used to execute a script. + * @deprecated use {@link ContextFactory#enter()} or + * {@link ContextFactory#call(ContextAction)} instead. + */ + public Context() + { + this(ContextFactory.getGlobal()); + } + + Context(ContextFactory factory) + { + assert factory != null; + this.factory = factory; + setLanguageVersion(VERSION_DEFAULT); + optimizationLevel = codegenClass != null ? 0 : -1; + maximumInterpreterStackDepth = Integer.MAX_VALUE; + } + + /** + * Get the current Context. + * + * The current Context is per-thread; this method looks up + * the Context associated with the current thread. <p> + * + * @return the Context associated with the current thread, or + * null if no context is associated with the current + * thread. + * @see ContextFactory#enterContext() + * @see ContextFactory#call(ContextAction) + */ + public static Context getCurrentContext() + { + Object helper = VMBridge.instance.getThreadContextHelper(); + return VMBridge.instance.getContext(helper); + } + + /** + * Same as calling {@link ContextFactory#enterContext()} on the global + * ContextFactory instance. + * @deprecated use {@link ContextFactory#enter()} or + * {@link ContextFactory#call(ContextAction)} instead as this method relies + * on usage of a static singleton "global" ContextFactory. + * @return a Context associated with the current thread + * @see #getCurrentContext() + * @see #exit() + * @see #call(ContextAction) + */ + public static Context enter() + { + return enter(null); + } + + /** + * Get a Context associated with the current thread, using + * the given Context if need be. + * <p> + * The same as <code>enter()</code> except that <code>cx</code> + * is associated with the current thread and returned if + * the current thread has no associated context and <code>cx</code> + * is not associated with any other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @deprecated use {@link ContextFactory#enterContext(Context)} instead as + * this method relies on usage of a static singleton "global" ContextFactory. + * @see ContextFactory#enterContext(Context) + * @see ContextFactory#call(ContextAction) + */ + public static Context enter(Context cx) + { + return enter(cx, ContextFactory.getGlobal()); + } + + static final Context enter(Context cx, ContextFactory factory) + { + Object helper = VMBridge.instance.getThreadContextHelper(); + Context old = VMBridge.instance.getContext(helper); + if (old != null) { + cx = old; + } else { + if (cx == null) { + cx = factory.makeContext(); + if (cx.enterCount != 0) { + throw new IllegalStateException("factory.makeContext() returned Context instance already associated with some thread"); + } + factory.onContextCreated(cx); + if (factory.isSealed() && !cx.isSealed()) { + cx.seal(null); + } + } else { + if (cx.enterCount != 0) { + throw new IllegalStateException("can not use Context instance already associated with some thread"); + } + } + VMBridge.instance.setContext(helper, cx); + } + ++cx.enterCount; + return cx; + } + + /** + * Exit a block of code requiring a Context. + * + * Calling <code>exit()</code> will remove the association between + * the current thread and a Context if the prior call to + * {@link ContextFactory#enterContext()} on this thread newly associated a + * Context with this thread. Once the current thread no longer has an + * associated Context, it cannot be used to execute JavaScript until it is + * again associated with a Context. + * @see ContextFactory#enterContext() + */ + public static void exit() + { + Object helper = VMBridge.instance.getThreadContextHelper(); + Context cx = VMBridge.instance.getContext(helper); + if (cx == null) { + throw new IllegalStateException( + "Calling Context.exit without previous Context.enter"); + } + if (cx.enterCount < 1) Kit.codeBug(); + if (--cx.enterCount == 0) { + VMBridge.instance.setContext(helper, null); + cx.factory.onContextReleased(cx); + } + } + + /** + * Call {@link ContextAction#run(Context cx)} + * using the Context instance associated with the current thread. + * If no Context is associated with the thread, then + * <tt>ContextFactory.getGlobal().makeContext()</tt> will be called to + * construct new Context instance. The instance will be temporary + * associated with the thread during call to + * {@link ContextAction#run(Context)}. + * @deprecated use {@link ContextFactory#call(ContextAction)} instead as + * this method relies on usage of a static singleton "global" + * ContextFactory. + * @return The result of {@link ContextAction#run(Context)}. + */ + public static Object call(ContextAction action) + { + return call(ContextFactory.getGlobal(), action); + } + + /** + * Call {@link + * Callable#call(Context cx, Scriptable scope, Scriptable thisObj, + * Object[] args)} + * using the Context instance associated with the current thread. + * If no Context is associated with the thread, then + * {@link ContextFactory#makeContext()} will be called to construct + * new Context instance. The instance will be temporary associated + * with the thread during call to {@link ContextAction#run(Context)}. + * <p> + * It is allowed but not advisable to use null for <tt>factory</tt> + * argument in which case the global static singleton ContextFactory + * instance will be used to create new context instances. + * @see ContextFactory#call(ContextAction) + */ + public static Object call(ContextFactory factory, final Callable callable, + final Scriptable scope, final Scriptable thisObj, + final Object[] args) + { + if(factory == null) { + factory = ContextFactory.getGlobal(); + } + return call(factory, new ContextAction() { + public Object run(Context cx) { + return callable.call(cx, scope, thisObj, args); + } + }); + } + + /** + * The method implements {@links ContextFactory#call(ContextAction)} logic. + */ + static Object call(ContextFactory factory, ContextAction action) { + Context cx = enter(null, factory); + try { + return action.run(cx); + } + finally { + exit(); + } + } + + /** + * @deprecated + * @see ContextFactory#addListener(ContextFactory.Listener) + * @see ContextFactory#getGlobal() + */ + public static void addContextListener(ContextListener listener) + { + // Special workaround for the debugger + String DBG = "org.mozilla.javascript.tools.debugger.Main"; + if (DBG.equals(listener.getClass().getName())) { + Class cl = listener.getClass(); + Class factoryClass = Kit.classOrNull( + "org.mozilla.javascript.ContextFactory"); + Class[] sig = { factoryClass }; + Object[] args = { ContextFactory.getGlobal() }; + try { + Method m = cl.getMethod("attachTo", sig); + m.invoke(listener, args); + } catch (Exception ex) { + RuntimeException rex = new RuntimeException(); + Kit.initCause(rex, ex); + throw rex; + } + return; + } + + ContextFactory.getGlobal().addListener(listener); + } + + /** + * @deprecated + * @see ContextFactory#removeListener(ContextFactory.Listener) + * @see ContextFactory#getGlobal() + */ + public static void removeContextListener(ContextListener listener) + { + ContextFactory.getGlobal().addListener(listener); + } + + /** + * Return {@link ContextFactory} instance used to create this Context. + */ + public final ContextFactory getFactory() + { + return factory; + } + + /** + * Checks if this is a sealed Context. A sealed Context instance does not + * allow to modify any of its properties and will throw an exception + * on any such attempt. + * @see #seal(Object sealKey) + */ + public final boolean isSealed() + { + return sealed; + } + + /** + * Seal this Context object so any attempt to modify any of its properties + * including calling {@link #enter()} and {@link #exit()} methods will + * throw an exception. + * <p> + * If <tt>sealKey</tt> is not null, calling + * {@link #unseal(Object sealKey)} with the same key unseals + * the object. If <tt>sealKey</tt> is null, unsealing is no longer possible. + * + * @see #isSealed() + * @see #unseal(Object) + */ + public final void seal(Object sealKey) + { + if (sealed) onSealedMutation(); + sealed = true; + this.sealKey = sealKey; + } + + /** + * Unseal previously sealed Context object. + * The <tt>sealKey</tt> argument should not be null and should match + * <tt>sealKey</tt> suplied with the last call to + * {@link #seal(Object)} or an exception will be thrown. + * + * @see #isSealed() + * @see #seal(Object sealKey) + */ + public final void unseal(Object sealKey) + { + if (sealKey == null) throw new IllegalArgumentException(); + if (this.sealKey != sealKey) throw new IllegalArgumentException(); + if (!sealed) throw new IllegalStateException(); + sealed = false; + this.sealKey = null; + } + + static void onSealedMutation() + { + throw new IllegalStateException(); + } + + /** + * Get the current language version. + * <p> + * The language version number affects JavaScript semantics as detailed + * in the overview documentation. + * + * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc. + */ + public final int getLanguageVersion() + { + return version; + } + + /** + * Set the language version. + * + * <p> + * Setting the language version will affect functions and scripts compiled + * subsequently. See the overview documentation for version-specific + * behavior. + * + * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc. + */ + public void setLanguageVersion(int version) + { + if (sealed) onSealedMutation(); + checkLanguageVersion(version); + Object listeners = propertyListeners; + if (listeners != null && version != this.version) { + firePropertyChangeImpl(listeners, languageVersionProperty, + new Integer(this.version), + new Integer(version)); + } + this.version = version; + } + + public static boolean isValidLanguageVersion(int version) + { + switch (version) { + case VERSION_DEFAULT: + case VERSION_1_0: + case VERSION_1_1: + case VERSION_1_2: + case VERSION_1_3: + case VERSION_1_4: + case VERSION_1_5: + case VERSION_1_6: + case VERSION_1_7: + return true; + } + return false; + } + + public static void checkLanguageVersion(int version) + { + if (isValidLanguageVersion(version)) { + return; + } + throw new IllegalArgumentException("Bad language version: "+version); + } + + /** + * Get the implementation version. + * + * <p> + * The implementation version is of the form + * <pre> + * "<i>name langVer</i> <code>release</code> <i>relNum date</i>" + * </pre> + * where <i>name</i> is the name of the product, <i>langVer</i> is + * the language version, <i>relNum</i> is the release number, and + * <i>date</i> is the release date for that specific + * release in the form "yyyy mm dd". + * + * @return a string that encodes the product, language version, release + * number, and date. + */ + public final String getImplementationVersion() + { + // XXX Probably it would be better to embed this directly into source + // with special build preprocessing but that would require some ant + // tweaking and then replacing token in resource files was simpler + if (implementationVersion == null) { + implementationVersion + = ScriptRuntime.getMessage0("implementation.version"); + } + return implementationVersion; + } + + /** + * Get the current error reporter. + * + * @see org.mozilla.javascript.ErrorReporter + */ + public final ErrorReporter getErrorReporter() + { + if (errorReporter == null) { + return DefaultErrorReporter.instance; + } + return errorReporter; + } + + /** + * Change the current error reporter. + * + * @return the previous error reporter + * @see org.mozilla.javascript.ErrorReporter + */ + public final ErrorReporter setErrorReporter(ErrorReporter reporter) + { + if (sealed) onSealedMutation(); + if (reporter == null) throw new IllegalArgumentException(); + ErrorReporter old = getErrorReporter(); + if (reporter == old) { + return old; + } + Object listeners = propertyListeners; + if (listeners != null) { + firePropertyChangeImpl(listeners, errorReporterProperty, + old, reporter); + } + this.errorReporter = reporter; + return old; + } + + /** + * Get the current locale. Returns the default locale if none has + * been set. + * + * @see java.util.Locale + */ + + public final Locale getLocale() + { + if (locale == null) + locale = Locale.getDefault(); + return locale; + } + + /** + * Set the current locale. + * + * @see java.util.Locale + */ + public final Locale setLocale(Locale loc) + { + if (sealed) onSealedMutation(); + Locale result = locale; + locale = loc; + return result; + } + + /** + * Register an object to receive notifications when a bound property + * has changed + * @see java.beans.PropertyChangeEvent + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + * @param l the listener + */ + public final void addPropertyChangeListener(PropertyChangeListener l) + { + if (sealed) onSealedMutation(); + propertyListeners = Kit.addListener(propertyListeners, l); + } + + /** + * Remove an object from the list of objects registered to receive + * notification of changes to a bounded property + * @see java.beans.PropertyChangeEvent + * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) + * @param l the listener + */ + public final void removePropertyChangeListener(PropertyChangeListener l) + { + if (sealed) onSealedMutation(); + propertyListeners = Kit.removeListener(propertyListeners, l); + } + + /** + * Notify any registered listeners that a bounded property has changed + * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + * @see java.beans.PropertyChangeListener + * @see java.beans.PropertyChangeEvent + * @param property the bound property + * @param oldValue the old value + * @param newValue the new value + */ + final void firePropertyChange(String property, Object oldValue, + Object newValue) + { + Object listeners = propertyListeners; + if (listeners != null) { + firePropertyChangeImpl(listeners, property, oldValue, newValue); + } + } + + private void firePropertyChangeImpl(Object listeners, String property, + Object oldValue, Object newValue) + { + for (int i = 0; ; ++i) { + Object l = Kit.getListener(listeners, i); + if (l == null) + break; + if (l instanceof PropertyChangeListener) { + PropertyChangeListener pcl = (PropertyChangeListener)l; + pcl.propertyChange(new PropertyChangeEvent( + this, property, oldValue, newValue)); + } + } + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportWarning(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = Context.getContext(); + if (cx.hasFeature(FEATURE_WARNING_AS_ERROR)) + reportError(message, sourceName, lineno, lineSource, lineOffset); + else + cx.getErrorReporter().warning(message, sourceName, lineno, + lineSource, lineOffset); + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportWarning(String message) + { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportWarning(message, filename, linep[0], null, 0); + } + + public static void reportWarning(String message, Throwable t) + { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Writer sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.println(message); + t.printStackTrace(pw); + pw.flush(); + Context.reportWarning(sw.toString(), filename, linep[0], null, 0); + } + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportError(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + cx.getErrorReporter().error(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message, sourceName, lineno, + lineSource, lineOffset); + } + } + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportError(String message) + { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportError(message, filename, linep[0], null, 0); + } + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return a runtime exception that will be thrown to terminate the + * execution of the script + * @see org.mozilla.javascript.ErrorReporter + */ + public static EvaluatorException reportRuntimeError(String message, + String sourceName, + int lineno, + String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + return cx.getErrorReporter(). + runtimeError(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message, sourceName, lineno, + lineSource, lineOffset); + } + } + + static EvaluatorException reportRuntimeError0(String messageId) + { + String msg = ScriptRuntime.getMessage0(messageId); + return reportRuntimeError(msg); + } + + static EvaluatorException reportRuntimeError1(String messageId, + Object arg1) + { + String msg = ScriptRuntime.getMessage1(messageId, arg1); + return reportRuntimeError(msg); + } + + static EvaluatorException reportRuntimeError2(String messageId, + Object arg1, Object arg2) + { + String msg = ScriptRuntime.getMessage2(messageId, arg1, arg2); + return reportRuntimeError(msg); + } + + static EvaluatorException reportRuntimeError3(String messageId, + Object arg1, Object arg2, + Object arg3) + { + String msg = ScriptRuntime.getMessage3(messageId, arg1, arg2, arg3); + return reportRuntimeError(msg); + } + + static EvaluatorException reportRuntimeError4(String messageId, + Object arg1, Object arg2, + Object arg3, Object arg4) + { + String msg + = ScriptRuntime.getMessage4(messageId, arg1, arg2, arg3, arg4); + return reportRuntimeError(msg); + } + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static EvaluatorException reportRuntimeError(String message) + { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + return Context.reportRuntimeError(message, filename, linep[0], null, 0); + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.<p> + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.<p> + * + * This method does not affect the Context it is called upon. + * + * @return the initialized scope + */ + public final ScriptableObject initStandardObjects() + { + return initStandardObjects(null, false); + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.<p> + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.<p> + * + * This method does not affect the Context it is called upon. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object which + * is an instance {@link ScriptableObject}. + */ + public final Scriptable initStandardObjects(ScriptableObject scope) + { + return initStandardObjects(scope, false); + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.<p> + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.<p> + * + * This method does not affect the Context it is called upon.<p> + * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added, changed, + * or removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object. + * @since 1.4R3 + */ + public ScriptableObject initStandardObjects(ScriptableObject scope, + boolean sealed) + { + return ScriptRuntime.initStandardObjects(this, scope, sealed); + } + + /** + * Get the singleton object that represents the JavaScript Undefined value. + */ + public static Object getUndefinedValue() + { + return Undefined.instance; + } + + /** + * Evaluate a JavaScript source string. + * + * The provided source name and line number are used for error messages + * and for producing debug information. + * + * @param scope the scope to execute in + * @param source the JavaScript source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the string + * @see org.mozilla.javascript.SecurityController + */ + public final Object evaluateString(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + { + Script script = compileString(source, sourceName, lineno, + securityDomain); + if (script != null) { + return script.exec(this, scope); + } else { + return null; + } + } + + /** + * Evaluate a reader as JavaScript source. + * + * All characters of the reader are consumed. + * + * @param scope the scope to execute in + * @param in the Reader to get JavaScript source from + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the source + * + * @exception IOException if an IOException was generated by the Reader + */ + public final Object evaluateReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException + { + Script script = compileReader(scope, in, sourceName, lineno, + securityDomain); + if (script != null) { + return script.exec(this, scope); + } else { + return null; + } + } + + /** + * Check whether a string is ready to be compiled. + * <p> + * stringIsCompilableUnit is intended to support interactive compilation of + * javascript. If compiling the string would result in an error + * that might be fixed by appending more source, this method + * returns false. In every other case, it returns true. + * <p> + * Interactive shells may accumulate source lines, using this + * method after each new line is appended to check whether the + * statement being entered is complete. + * + * @param source the source buffer to check + * @return whether the source is ready for compilation + * @since 1.4 Release 2 + */ + public final boolean stringIsCompilableUnit(String source) + { + boolean errorseen = false; + CompilerEnvirons compilerEnv = new CompilerEnvirons(); + compilerEnv.initFromContext(this); + // no source name or source text manager, because we're just + // going to throw away the result. + compilerEnv.setGeneratingSource(false); + /*APPJET*/ + Parser p = InformativeParser.makeParser(compilerEnv, + DefaultErrorReporter.instance); + try { + p.parse(source, null, 1); + } catch (EvaluatorException ee) { + errorseen = true; + } + // Return false only if an error occurred as a result of reading past + // the end of the file, i.e. if the source could be fixed by + // appending more source. + if (errorseen && p.eof()) + return false; + else + return true; + } + + /** + * @deprecated + * @see #compileReader(Reader in, String sourceName, int lineno, + * Object securityDomain) + */ + public final Script compileReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException + { + return compileReader(in, sourceName, lineno, securityDomain); + } + + /** + * Compiles the source in the given reader. + * <p> + * Returns a script that may later be executed. + * Will consume all the source in the reader. + * + * @param in the input reader + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return a script that may later be executed + * @exception IOException if an IOException was generated by the Reader + * @see org.mozilla.javascript.Script + */ + public final Script compileReader(Reader in, String sourceName, + int lineno, Object securityDomain) + throws IOException + { + if (lineno < 0) { + // For compatibility IllegalArgumentException can not be thrown here + lineno = 0; + } + return (Script) compileImpl(null, in, null, sourceName, lineno, + securityDomain, false, null, null); + } + + /** + * Compiles the source in the given string. + * <p> + * Returns a script that may later be executed. + * + * @param source the source string + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return a script that may later be executed + * @see org.mozilla.javascript.Script + */ + public final Script compileString(String source, + String sourceName, int lineno, + Object securityDomain) + { + if (lineno < 0) { + // For compatibility IllegalArgumentException can not be thrown here + lineno = 0; + } + return compileString(source, null, null, sourceName, lineno, + securityDomain); + } + + final Script compileString(String source, + Evaluator compiler, + ErrorReporter compilationErrorReporter, + String sourceName, int lineno, + Object securityDomain) + { + try { + return (Script) compileImpl(null, null, source, sourceName, lineno, + securityDomain, false, + compiler, compilationErrorReporter); + } catch (IOException ex) { + // Should not happen when dealing with source as string + throw new RuntimeException(); + } + } + + /** + * Compile a JavaScript function. + * <p> + * The function source must be a function definition as defined by + * ECMA (e.g., "function f(a) { return a; }"). + * + * @param scope the scope to compile relative to + * @param source the function definition source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return a Function that may later be called + * @see org.mozilla.javascript.Function + */ + public final Function compileFunction(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + { + return compileFunction(scope, source, null, null, sourceName, lineno, + securityDomain); + } + + final Function compileFunction(Scriptable scope, String source, + Evaluator compiler, + ErrorReporter compilationErrorReporter, + String sourceName, int lineno, + Object securityDomain) + { + try { + return (Function) compileImpl(scope, null, source, sourceName, + lineno, securityDomain, true, + compiler, compilationErrorReporter); + } + catch (IOException ioe) { + // Should never happen because we just made the reader + // from a String + throw new RuntimeException(); + } + } + + /** + * Decompile the script. + * <p> + * The canonical source of the script is returned. + * + * @param script the script to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the script source + */ + public final String decompileScript(Script script, int indent) + { + NativeFunction scriptImpl = (NativeFunction) script; + return scriptImpl.decompile(indent, 0); + } + + /** + * Decompile a JavaScript Function. + * <p> + * Decompiles a previously compiled JavaScript function object to + * canonical source. + * <p> + * Returns function body of '[native code]' if no decompilation + * information is available. + * + * @param fun the JavaScript function to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the function source + */ + public final String decompileFunction(Function fun, int indent) + { + if (fun instanceof BaseFunction) + return ((BaseFunction)fun).decompile(indent, 0); + else + return "function " + fun.getClassName() + + "() {\n\t[native code]\n}\n"; + } + + /** + * Decompile the body of a JavaScript Function. + * <p> + * Decompiles the body a previously compiled JavaScript Function + * object to canonical source, omitting the function header and + * trailing brace. + * + * Returns '[native code]' if no decompilation information is available. + * + * @param fun the JavaScript function to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the function body source. + */ + public final String decompileFunctionBody(Function fun, int indent) + { + if (fun instanceof BaseFunction) { + BaseFunction bf = (BaseFunction)fun; + return bf.decompile(indent, Decompiler.ONLY_BODY_FLAG); + } + // ALERT: not sure what the right response here is. + return "[native code]\n"; + } + + /** + * Create a new JavaScript object. + * + * Equivalent to evaluating "new Object()". + * @param scope the scope to search for the constructor and to evaluate + * against + * @return the new object + */ + public final Scriptable newObject(Scriptable scope) + { + return newObject(scope, "Object", ScriptRuntime.emptyArgs); + } + + /** + * Create a new JavaScript object by executing the named constructor. + * + * The call <code>newObject(scope, "Foo")</code> is equivalent to + * evaluating "new Foo()". + * + * @param scope the scope to search for the constructor and to evaluate against + * @param constructorName the name of the constructor to call + * @return the new object + */ + public final Scriptable newObject(Scriptable scope, String constructorName) + { + return newObject(scope, constructorName, ScriptRuntime.emptyArgs); + } + + /** + * Creates a new JavaScript object by executing the named constructor. + * + * Searches <code>scope</code> for the named constructor, calls it with + * the given arguments, and returns the result.<p> + * + * The code + * <pre> + * Object[] args = { "a", "b" }; + * newObject(scope, "Foo", args)</pre> + * is equivalent to evaluating "new Foo('a', 'b')", assuming that the Foo + * constructor has been defined in <code>scope</code>. + * + * @param scope The scope to search for the constructor and to evaluate + * against + * @param constructorName the name of the constructor to call + * @param args the array of arguments for the constructor + * @return the new object + */ + public final Scriptable newObject(Scriptable scope, String constructorName, + Object[] args) + { + scope = ScriptableObject.getTopLevelScope(scope); + Function ctor = ScriptRuntime.getExistingCtor(this, scope, + constructorName); + if (args == null) { args = ScriptRuntime.emptyArgs; } + return ctor.construct(this, scope, args); + } + + /** + * Create an array with a specified initial length. + * <p> + * @param scope the scope to create the object in + * @param length the initial length (JavaScript arrays may have + * additional properties added dynamically). + * @return the new array object + */ + public final Scriptable newArray(Scriptable scope, int length) + { + NativeArray result = new NativeArray(length); + ScriptRuntime.setObjectProtoAndParent(result, scope); + return result; + } + + /** + * Create an array with a set of initial elements. + * + * @param scope the scope to create the object in. + * @param elements the initial elements. Each object in this array + * must be an acceptable JavaScript type and type + * of array should be exactly Object[], not + * SomeObjectSubclass[]. + * @return the new array object. + */ + public final Scriptable newArray(Scriptable scope, Object[] elements) + { + if (elements.getClass().getComponentType() != ScriptRuntime.ObjectClass) + throw new IllegalArgumentException(); + NativeArray result = new NativeArray(elements); + ScriptRuntime.setObjectProtoAndParent(result, scope); + return result; + } + + /** + * Get the elements of a JavaScript array. + * <p> + * If the object defines a length property convertible to double number, + * then the number is converted Uint32 value as defined in Ecma 9.6 + * and Java array of that size is allocated. + * The array is initialized with the values obtained by + * calling get() on object for each value of i in [0,length-1]. If + * there is not a defined value for a property the Undefined value + * is used to initialize the corresponding element in the array. The + * Java array is then returned. + * If the object doesn't define a length property or it is not a number, + * empty array is returned. + * @param object the JavaScript array or array-like object + * @return a Java array of objects + * @since 1.4 release 2 + */ + public final Object[] getElements(Scriptable object) + { + return ScriptRuntime.getArrayElements(object); + } + + /** + * Convert the value to a JavaScript boolean value. + * <p> + * See ECMA 9.2. + * + * @param value a JavaScript value + * @return the corresponding boolean value converted using + * the ECMA rules + */ + public static boolean toBoolean(Object value) + { + return ScriptRuntime.toBoolean(value); + } + + /** + * Convert the value to a JavaScript Number value. + * <p> + * Returns a Java double for the JavaScript Number. + * <p> + * See ECMA 9.3. + * + * @param value a JavaScript value + * @return the corresponding double value converted using + * the ECMA rules + */ + public static double toNumber(Object value) + { + return ScriptRuntime.toNumber(value); + } + + /** + * Convert the value to a JavaScript String value. + * <p> + * See ECMA 9.8. + * <p> + * @param value a JavaScript value + * @return the corresponding String value converted using + * the ECMA rules + */ + public static String toString(Object value) + { + return ScriptRuntime.toString(value); + } + + /** + * Convert the value to an JavaScript object value. + * <p> + * Note that a scope must be provided to look up the constructors + * for Number, Boolean, and String. + * <p> + * See ECMA 9.9. + * <p> + * Additionally, arbitrary Java objects and classes will be + * wrapped in a Scriptable object with its Java fields and methods + * reflected as JavaScript properties of the object. + * + * @param value any Java object + * @param scope global scope containing constructors for Number, + * Boolean, and String + * @return new JavaScript object + */ + public static Scriptable toObject(Object value, Scriptable scope) + { + return ScriptRuntime.toObject(scope, value); + } + + /** + * @deprecated + * @see #toObject(Object, Scriptable) + */ + public static Scriptable toObject(Object value, Scriptable scope, + Class staticType) + { + return ScriptRuntime.toObject(scope, value); + } + + /** + * Convenient method to convert java value to its closest representation + * in JavaScript. + * <p> + * If value is an instance of String, Number, Boolean, Function or + * Scriptable, it is returned as it and will be treated as the corresponding + * JavaScript type of string, number, boolean, function and object. + * <p> + * Note that for Number instances during any arithmetic operation in + * JavaScript the engine will always use the result of + * <tt>Number.doubleValue()</tt> resulting in a precision loss if + * the number can not fit into double. + * <p> + * If value is an instance of Character, it will be converted to string of + * length 1 and its JavaScript type will be string. + * <p> + * The rest of values will be wrapped as LiveConnect objects + * by calling {@link WrapFactory#wrap(Context cx, Scriptable scope, + * Object obj, Class staticType)} as in: + * <pre> + * Context cx = Context.getCurrentContext(); + * return cx.getWrapFactory().wrap(cx, scope, value, null); + * </pre> + * + * @param value any Java object + * @param scope top scope object + * @return value suitable to pass to any API that takes JavaScript values. + */ + public static Object javaToJS(Object value, Scriptable scope) + { + if (value instanceof String || value instanceof Number + || value instanceof Boolean || value instanceof Scriptable) + { + return value; + } else if (value instanceof Character) { + return String.valueOf(((Character)value).charValue()); + } else { + Context cx = Context.getContext(); + return cx.getWrapFactory().wrap(cx, scope, value, null); + } + } + + /** + * Convert a JavaScript value into the desired type. + * Uses the semantics defined with LiveConnect3 and throws an + * Illegal argument exception if the conversion cannot be performed. + * @param value the JavaScript value to convert + * @param desiredType the Java type to convert to. Primitive Java + * types are represented using the TYPE fields in the corresponding + * wrapper class in java.lang. + * @return the converted value + * @throws EvaluatorException if the conversion cannot be performed + */ + public static Object jsToJava(Object value, Class desiredType) + throws EvaluatorException + { + return NativeJavaObject.coerceTypeImpl(desiredType, value); + } + + /** + * @deprecated + * @see #jsToJava(Object, Class) + * @throws IllegalArgumentException if the conversion cannot be performed. + * Note that {@link #jsToJava(Object, Class)} throws + * {@link EvaluatorException} instead. + */ + public static Object toType(Object value, Class desiredType) + throws IllegalArgumentException + { + try { + return jsToJava(value, desiredType); + } catch (EvaluatorException ex) { + IllegalArgumentException + ex2 = new IllegalArgumentException(ex.getMessage()); + Kit.initCause(ex2, ex); + throw ex2; + } + } + + /** + * Rethrow the exception wrapping it as the script runtime exception. + * Unless the exception is instance of {@link EcmaError} or + * {@link EvaluatorException} it will be wrapped as + * {@link WrappedException}, a subclass of {@link EvaluatorException}. + * The resulting exception object always contains + * source name and line number of script that triggered exception. + * <p> + * This method always throws an exception, its return value is provided + * only for convenience to allow a usage like: + * <pre> + * throw Context.throwAsScriptRuntimeEx(ex); + * </pre> + * to indicate that code after the method is unreachable. + * @throws EvaluatorException + * @throws EcmaError + */ + public static RuntimeException throwAsScriptRuntimeEx(Throwable e) + { + while ((e instanceof InvocationTargetException)) { + e = ((InvocationTargetException) e).getTargetException(); + } + // special handling of Error so scripts would not catch them + if (e instanceof Error) { + Context cx = getContext(); + if (cx == null || + !cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) + { + throw (Error)e; + } + } + if (e instanceof RhinoException) { + throw (RhinoException)e; + } + throw new WrappedException(e); + } + + /** + * Tell whether debug information is being generated. + * @since 1.3 + */ + public final boolean isGeneratingDebug() + { + return generatingDebug; + } + + /** + * Specify whether or not debug information should be generated. + * <p> + * Setting the generation of debug information on will set the + * optimization level to zero. + * @since 1.3 + */ + public final void setGeneratingDebug(boolean generatingDebug) + { + if (sealed) onSealedMutation(); + generatingDebugChanged = true; + if (generatingDebug && getOptimizationLevel() > 0) + setOptimizationLevel(0); + this.generatingDebug = generatingDebug; + } + + /** + * Tell whether source information is being generated. + * @since 1.3 + */ + public final boolean isGeneratingSource() + { + return generatingSource; + } + + /** + * Specify whether or not source information should be generated. + * <p> + * Without source information, evaluating the "toString" method + * on JavaScript functions produces only "[native code]" for + * the body of the function. + * Note that code generated without source is not fully ECMA + * conformant. + * @since 1.3 + */ + public final void setGeneratingSource(boolean generatingSource) + { + if (sealed) onSealedMutation(); + this.generatingSource = generatingSource; + } + + /** + * Get the current optimization level. + * <p> + * The optimization level is expressed as an integer between -1 and + * 9. + * @since 1.3 + * + */ + public final int getOptimizationLevel() + { + return optimizationLevel; + } + + /** + * Set the current optimization level. + * <p> + * The optimization level is expected to be an integer between -1 and + * 9. Any negative values will be interpreted as -1, and any values + * greater than 9 will be interpreted as 9. + * An optimization level of -1 indicates that interpretive mode will + * always be used. Levels 0 through 9 indicate that class files may + * be generated. Higher optimization levels trade off compile time + * performance for runtime performance. + * The optimizer level can't be set greater than -1 if the optimizer + * package doesn't exist at run time. + * @param optimizationLevel an integer indicating the level of + * optimization to perform + * @since 1.3 + * + */ + public final void setOptimizationLevel(int optimizationLevel) + { + if (sealed) onSealedMutation(); + if (optimizationLevel == -2) { + // To be compatible with Cocoon fork + optimizationLevel = -1; + } + checkOptimizationLevel(optimizationLevel); + if (codegenClass == null) + optimizationLevel = -1; + this.optimizationLevel = optimizationLevel; + } + + public static boolean isValidOptimizationLevel(int optimizationLevel) + { + return -1 <= optimizationLevel && optimizationLevel <= 9; + } + + public static void checkOptimizationLevel(int optimizationLevel) + { + if (isValidOptimizationLevel(optimizationLevel)) { + return; + } + throw new IllegalArgumentException( + "Optimization level outside [-1..9]: "+optimizationLevel); + } + + /** + * Returns the maximum stack depth (in terms of number of call frames) + * allowed in a single invocation of interpreter. If the set depth would be + * exceeded, the interpreter will throw an EvaluatorException in the script. + * Defaults to Integer.MAX_VALUE. The setting only has effect for + * interpreted functions (those compiled with optimization level set to -1). + * As the interpreter doesn't use the Java stack but rather manages its own + * stack in the heap memory, a runaway recursion in interpreted code would + * eventually consume all available memory and cause OutOfMemoryError + * instead of a StackOverflowError limited to only a single thread. This + * setting helps prevent such situations. + * + * @return The current maximum interpreter stack depth. + */ + public final int getMaximumInterpreterStackDepth() + { + return maximumInterpreterStackDepth; + } + + /** + * Sets the maximum stack depth (in terms of number of call frames) + * allowed in a single invocation of interpreter. If the set depth would be + * exceeded, the interpreter will throw an EvaluatorException in the script. + * Defaults to Integer.MAX_VALUE. The setting only has effect for + * interpreted functions (those compiled with optimization level set to -1). + * As the interpreter doesn't use the Java stack but rather manages its own + * stack in the heap memory, a runaway recursion in interpreted code would + * eventually consume all available memory and cause OutOfMemoryError + * instead of a StackOverflowError limited to only a single thread. This + * setting helps prevent such situations. + * + * @param max the new maximum interpreter stack depth + * @throws IllegalStateException if this context's optimization level is not + * -1 + * @throws IllegalArgumentException if the new depth is not at least 1 + */ + public final void setMaximumInterpreterStackDepth(int max) + { + if(sealed) onSealedMutation(); + if(optimizationLevel != -1) { + throw new IllegalStateException("Cannot set maximumInterpreterStackDepth when optimizationLevel != -1"); + } + if(max < 1) { + throw new IllegalArgumentException("Cannot set maximumInterpreterStackDepth to less than 1"); + } + maximumInterpreterStackDepth = max; + } + + /** + * Set the security controller for this context. + * <p> SecurityController may only be set if it is currently null + * and {@link SecurityController#hasGlobal()} is <tt>false</tt>. + * Otherwise a SecurityException is thrown. + * @param controller a SecurityController object + * @throws SecurityException if there is already a SecurityController + * object for this Context or globally installed. + * @see SecurityController#initGlobal(SecurityController controller) + * @see SecurityController#hasGlobal() + */ + public final void setSecurityController(SecurityController controller) + { + if (sealed) onSealedMutation(); + if (controller == null) throw new IllegalArgumentException(); + if (securityController != null) { + throw new SecurityException("Can not overwrite existing SecurityController object"); + } + if (SecurityController.hasGlobal()) { + throw new SecurityException("Can not overwrite existing global SecurityController object"); + } + securityController = controller; + } + + /** + * Set the LiveConnect access filter for this context. + * <p> {@link ClassShutter} may only be set if it is currently null. + * Otherwise a SecurityException is thrown. + * @param shutter a ClassShutter object + * @throws SecurityException if there is already a ClassShutter + * object for this Context + */ + public final void setClassShutter(ClassShutter shutter) + { + if (sealed) onSealedMutation(); + if (shutter == null) throw new IllegalArgumentException(); + if (classShutter != null) { + throw new SecurityException("Cannot overwrite existing " + + "ClassShutter object"); + } + classShutter = shutter; + } + + final ClassShutter getClassShutter() + { + return classShutter; + } + + /** + * Get a value corresponding to a key. + * <p> + * Since the Context is associated with a thread it can be + * used to maintain values that can be later retrieved using + * the current thread. + * <p> + * Note that the values are maintained with the Context, so + * if the Context is disassociated from the thread the values + * cannot be retrieved. Also, if private data is to be maintained + * in this manner the key should be a java.lang.Object + * whose reference is not divulged to untrusted code. + * @param key the key used to lookup the value + * @return a value previously stored using putThreadLocal. + */ + public final Object getThreadLocal(Object key) + { + if (hashtable == null) + return null; + return hashtable.get(key); + } + + /** + * Put a value that can later be retrieved using a given key. + * <p> + * @param key the key used to index the value + * @param value the value to save + */ + public final void putThreadLocal(Object key, Object value) + { + if (sealed) onSealedMutation(); + if (hashtable == null) + hashtable = new Hashtable(); + hashtable.put(key, value); + } + + /** + * Remove values from thread-local storage. + * @param key the key for the entry to remove. + * @since 1.5 release 2 + */ + public final void removeThreadLocal(Object key) + { + if (sealed) onSealedMutation(); + if (hashtable == null) + return; + hashtable.remove(key); + } + + /** + * @deprecated + * @see #FEATURE_DYNAMIC_SCOPE + * @see #hasFeature(int) + */ + public final boolean hasCompileFunctionsWithDynamicScope() + { + return compileFunctionsWithDynamicScopeFlag; + } + + /** + * @deprecated + * @see #FEATURE_DYNAMIC_SCOPE + * @see #hasFeature(int) + */ + public final void setCompileFunctionsWithDynamicScope(boolean flag) + { + if (sealed) onSealedMutation(); + compileFunctionsWithDynamicScopeFlag = flag; + } + + /** + * @deprecated + * @see ClassCache#get(Scriptable) + * @see ClassCache#setCachingEnabled(boolean) + */ + public static void setCachingEnabled(boolean cachingEnabled) + { + } + + /** + * Set a WrapFactory for this Context. + * <p> + * The WrapFactory allows custom object wrapping behavior for + * Java object manipulated with JavaScript. + * @see WrapFactory + * @since 1.5 Release 4 + */ + public final void setWrapFactory(WrapFactory wrapFactory) + { + if (sealed) onSealedMutation(); + if (wrapFactory == null) throw new IllegalArgumentException(); + this.wrapFactory = wrapFactory; + } + + /** + * Return the current WrapFactory, or null if none is defined. + * @see WrapFactory + * @since 1.5 Release 4 + */ + public final WrapFactory getWrapFactory() + { + if (wrapFactory == null) { + wrapFactory = new WrapFactory(); + } + return wrapFactory; + } + + /** + * Return the current debugger. + * @return the debugger, or null if none is attached. + */ + public final Debugger getDebugger() + { + return debugger; + } + + /** + * Return the debugger context data associated with current context. + * @return the debugger data, or null if debugger is not attached + */ + public final Object getDebuggerContextData() + { + return debuggerData; + } + + /** + * Set the associated debugger. + * @param debugger the debugger to be used on callbacks from + * the engine. + * @param contextData arbitrary object that debugger can use to store + * per Context data. + */ + public final void setDebugger(Debugger debugger, Object contextData) + { + if (sealed) onSealedMutation(); + this.debugger = debugger; + debuggerData = contextData; + } + + /** + * Return DebuggableScript instance if any associated with the script. + * If callable supports DebuggableScript implementation, the method + * returns it. Otherwise null is returned. + */ + public static DebuggableScript getDebuggableView(Script script) + { + if (script instanceof NativeFunction) { + return ((NativeFunction)script).getDebuggableView(); + } + return null; + } + + /** + * Controls certain aspects of script semantics. + * Should be overwritten to alter default behavior. + * <p> + * The default implementation calls + * {@link ContextFactory#hasFeature(Context cx, int featureIndex)} + * that allows to customize Context behavior without introducing + * Context subclasses. {@link ContextFactory} documentation gives + * an example of hasFeature implementation. + * + * @param featureIndex feature index to check + * @return true if the <code>featureIndex</code> feature is turned on + * @see #FEATURE_NON_ECMA_GET_YEAR + * @see #FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME + * @see #FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER + * @see #FEATURE_TO_STRING_AS_SOURCE + * @see #FEATURE_PARENT_PROTO_PROPRTIES + * @see #FEATURE_E4X + * @see #FEATURE_DYNAMIC_SCOPE + * @see #FEATURE_STRICT_VARS + * @see #FEATURE_STRICT_EVAL + * @see #FEATURE_LOCATION_INFORMATION_IN_ERROR + * @see #FEATURE_STRICT_MODE + * @see #FEATURE_WARNING_AS_ERROR + * @see #FEATURE_ENHANCED_JAVA_ACCESS + */ + public boolean hasFeature(int featureIndex) + { + ContextFactory f = getFactory(); + return f.hasFeature(this, featureIndex); + } + + /** + Returns an object which specifies an E4X implementation to use within + this <code>Context</code>. Note + that the XMLLib.Factory interface should be considered experimental. + + The default implementation uses the implementation provided by this + <code>Context</code>'s {@link ContextFactory}. + + @return An XMLLib.Factory. Should not return <code>null</code> if + {@link #FEATURE_E4X} is enabled. See {@link #hasFeature}. + */ + public XMLLib.Factory getE4xImplementationFactory() { + return getFactory().getE4xImplementationFactory(); + } + + /** + * Get threshold of executed instructions counter that triggers call to + * <code>observeInstructionCount()</code>. + * When the threshold is zero, instruction counting is disabled, + * otherwise each time the run-time executes at least the threshold value + * of script instructions, <code>observeInstructionCount()</code> will + * be called. + */ + public final int getInstructionObserverThreshold() + { + return instructionThreshold; + } + + /** + * Set threshold of executed instructions counter that triggers call to + * <code>observeInstructionCount()</code>. + * When the threshold is zero, instruction counting is disabled, + * otherwise each time the run-time executes at least the threshold value + * of script instructions, <code>observeInstructionCount()</code> will + * be called.<p/> + * Note that the meaning of "instruction" is not guaranteed to be + * consistent between compiled and interpretive modes: executing a given + * script or function in the different modes will result in different + * instruction counts against the threshold. + * {@link #setGenerateObserverCount} is called with true if + * <code>threshold</code> is greater than zero, false otherwise. + * @param threshold The instruction threshold + */ + public final void setInstructionObserverThreshold(int threshold) + { + if (sealed) onSealedMutation(); + if (threshold < 0) throw new IllegalArgumentException(); + instructionThreshold = threshold; + setGenerateObserverCount(threshold > 0); + } + + /** + * Turn on or off generation of code with callbacks to + * track the count of executed instructions. + * Currently only affects JVM byte code generation: this slows down the + * generated code, but code generated without the callbacks will not + * be counted toward instruction thresholds. Rhino's interpretive + * mode does instruction counting without inserting callbacks, so + * there is no requirement to compile code differently. + * @param generateObserverCount if true, generated code will contain + * calls to accumulate an estimate of the instructions executed. + */ + public void setGenerateObserverCount(boolean generateObserverCount) { + this.generateObserverCount = generateObserverCount; + } + + /** + * Allow application to monitor counter of executed script instructions + * in Context subclasses. + * Run-time calls this when instruction counting is enabled and the counter + * reaches limit set by <code>setInstructionObserverThreshold()</code>. + * The method is useful to observe long running scripts and if necessary + * to terminate them. + * <p> + * The instruction counting support is available only for interpreted + * scripts generated when the optimization level is set to -1. + * <p> + * The default implementation calls + * {@link ContextFactory#observeInstructionCount(Context cx, + * int instructionCount)} + * that allows to customize Context behavior without introducing + * Context subclasses. + * + * @param instructionCount amount of script instruction executed since + * last call to <code>observeInstructionCount</code> + * @throws Error to terminate the script + * @see #setOptimizationLevel(int) + */ + protected void observeInstructionCount(int instructionCount) + { + ContextFactory f = getFactory(); + f.observeInstructionCount(this, instructionCount); + } + + /** + * Create class loader for generated classes. + * The method calls {@link ContextFactory#createClassLoader(ClassLoader)} + * using the result of {@link #getFactory()}. + */ + public GeneratedClassLoader createClassLoader(ClassLoader parent) + { + ContextFactory f = getFactory(); + return f.createClassLoader(parent); + } + + public final ClassLoader getApplicationClassLoader() + { + if (applicationClassLoader == null) { + ContextFactory f = getFactory(); + ClassLoader loader = f.getApplicationClassLoader(); + if (loader == null) { + ClassLoader threadLoader + = VMBridge.instance.getCurrentThreadClassLoader(); + if (threadLoader != null + && Kit.testIfCanLoadRhinoClasses(threadLoader)) + { + // Thread.getContextClassLoader is not cached since + // its caching prevents it from GC which may lead to + // a memory leak and hides updates to + // Thread.getContextClassLoader + return threadLoader; + } + // Thread.getContextClassLoader can not load Rhino classes, + // try to use the loader of ContextFactory or Context + // subclasses. + Class fClass = f.getClass(); + if (fClass != ScriptRuntime.ContextFactoryClass) { + loader = fClass.getClassLoader(); + } else { + loader = getClass().getClassLoader(); + } + } + applicationClassLoader = loader; + } + return applicationClassLoader; + } + + public final void setApplicationClassLoader(ClassLoader loader) + { + if (sealed) onSealedMutation(); + if (loader == null) { + // restore default behaviour + applicationClassLoader = null; + return; + } + if (!Kit.testIfCanLoadRhinoClasses(loader)) { + throw new IllegalArgumentException( + "Loader can not resolve Rhino classes"); + } + applicationClassLoader = loader; + } + + /********** end of API **********/ + + /** + * Internal method that reports an error for missing calls to + * enter(). + */ + static Context getContext() + { + Context cx = getCurrentContext(); + if (cx == null) { + throw new RuntimeException( + "No Context associated with current Thread"); + } + return cx; + } + + private Object compileImpl(Scriptable scope, + Reader sourceReader, String sourceString, + String sourceName, int lineno, + Object securityDomain, boolean returnFunction, + Evaluator compiler, + ErrorReporter compilationErrorReporter) + throws IOException + { + if(sourceName == null) { + sourceName = "unnamed script"; + } + if (securityDomain != null && getSecurityController() == null) { + throw new IllegalArgumentException( + "securityDomain should be null if setSecurityController() was never called"); + } + + // One of sourceReader or sourceString has to be null + if (!(sourceReader == null ^ sourceString == null)) Kit.codeBug(); + // scope should be given if and only if compiling function + if (!(scope == null ^ returnFunction)) Kit.codeBug(); + + CompilerEnvirons compilerEnv = new CompilerEnvirons(); + compilerEnv.initFromContext(this); + if (compilationErrorReporter == null) { + compilationErrorReporter = compilerEnv.getErrorReporter(); + } + + if (debugger != null) { + if (sourceReader != null) { + sourceString = Kit.readReader(sourceReader); + sourceReader = null; + } + } + + /*APPJET*/ + Parser p = InformativeParser.makeParser(compilerEnv, + compilationErrorReporter); + if (returnFunction) { + p.calledByCompileFunction = true; + } + ScriptOrFnNode tree; + if (sourceString != null) { + tree = p.parse(sourceString, sourceName, lineno); + } else { + tree = p.parse(sourceReader, sourceName, lineno); + } + if (returnFunction) { + if (!(tree.getFunctionCount() == 1 + && tree.getFirstChild() != null + && tree.getFirstChild().getType() == Token.FUNCTION)) + { + // XXX: the check just look for the first child + // and allows for more nodes after it for compatibility + // with sources like function() {};;; + throw new IllegalArgumentException( + "compileFunction only accepts source with single JS function: "+sourceString); + } + } + + if (compiler == null) { + compiler = createCompiler(); + } + + String encodedSource = p.getEncodedSource(); + + Object bytecode = compiler.compile(compilerEnv, + tree, encodedSource, + returnFunction); + + if (debugger != null) { + if (sourceString == null) Kit.codeBug(); + if (bytecode instanceof DebuggableScript) { + DebuggableScript dscript = (DebuggableScript)bytecode; + notifyDebugger_r(this, dscript, sourceString); + } else { + throw new RuntimeException("NOT SUPPORTED"); + } + } + + Object result; + if (returnFunction) { + result = compiler.createFunctionObject(this, scope, bytecode, securityDomain); + } else { + result = compiler.createScriptObject(bytecode, securityDomain); + } + + return result; + } + + private static void notifyDebugger_r(Context cx, DebuggableScript dscript, + String debugSource) + { + cx.debugger.handleCompilationDone(cx, dscript, debugSource); + for (int i = 0; i != dscript.getFunctionCount(); ++i) { + notifyDebugger_r(cx, dscript.getFunction(i), debugSource); + } + } + + private static Class codegenClass = Kit.classOrNull( + "org.mozilla.javascript.optimizer.Codegen"); + private static Class interpreterClass = Kit.classOrNull( + "org.mozilla.javascript.Interpreter"); + + private Evaluator createCompiler() + { + Evaluator result = null; + if (optimizationLevel >= 0 && codegenClass != null) { + result = (Evaluator)Kit.newInstanceOrNull(codegenClass); + } + if (result == null) { + result = createInterpreter(); + } + return result; + } + + static Evaluator createInterpreter() + { + return (Evaluator)Kit.newInstanceOrNull(interpreterClass); + } + + static String getSourcePositionFromStack(int[] linep) + { + Context cx = getCurrentContext(); + if (cx == null) + return null; + if (cx.lastInterpreterFrame != null) { + Evaluator evaluator = createInterpreter(); + if (evaluator != null) + return evaluator.getSourcePositionFromStack(cx, linep); + } + /** + * A bit of a hack, but the only way to get filename and line + * number from an enclosing frame. + */ + CharArrayWriter writer = new CharArrayWriter(); + RuntimeException re = new RuntimeException(); + re.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + int open = -1; + int close = -1; + int colon = -1; + for (int i=0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == ':') + colon = i; + else if (c == '(') + open = i; + else if (c == ')') + close = i; + else if (c == '\n' && open != -1 && close != -1 && colon != -1 && + open < colon && colon < close) + { + String fileStr = s.substring(open + 1, colon); + if (!fileStr.endsWith(".java")) { + String lineStr = s.substring(colon + 1, close); + try { + linep[0] = Integer.parseInt(lineStr); + if (linep[0] < 0) { + linep[0] = 0; + } + return fileStr; + } + catch (NumberFormatException e) { + // fall through + } + } + open = close = colon = -1; + } + } + + return null; + } + + RegExpProxy getRegExpProxy() + { + if (regExpProxy == null) { + Class cl = Kit.classOrNull( + "org.mozilla.javascript.regexp.RegExpImpl"); + if (cl != null) { + regExpProxy = (RegExpProxy)Kit.newInstanceOrNull(cl); + } + } + return regExpProxy; + } + + final boolean isVersionECMA1() + { + return version == VERSION_DEFAULT || version >= VERSION_1_3; + } + +// The method must NOT be public or protected + SecurityController getSecurityController() + { + SecurityController global = SecurityController.global(); + if (global != null) { + return global; + } + return securityController; + } + + public final boolean isGeneratingDebugChanged() + { + return generatingDebugChanged; + } + + /** + * Add a name to the list of names forcing the creation of real + * activation objects for functions. + * + * @param name the name of the object to add to the list + */ + public void addActivationName(String name) + { + if (sealed) onSealedMutation(); + if (activationNames == null) + activationNames = new Hashtable(5); + activationNames.put(name, name); + } + + /** + * Check whether the name is in the list of names of objects + * forcing the creation of activation objects. + * + * @param name the name of the object to test + * + * @return true if an function activation object is needed. + */ + public final boolean isActivationNeeded(String name) + { + return activationNames != null && activationNames.containsKey(name); + } + + /** + * Remove a name from the list of names forcing the creation of real + * activation objects for functions. + * + * @param name the name of the object to remove from the list + */ + public void removeActivationName(String name) + { + if (sealed) onSealedMutation(); + if (activationNames != null) + activationNames.remove(name); + } + + private static String implementationVersion; + + private final ContextFactory factory; + private boolean sealed; + private Object sealKey; + + Scriptable topCallScope; + NativeCall currentActivationCall; + XMLLib cachedXMLLib; + + // for Objects, Arrays to tag themselves as being printed out, + // so they don't print themselves out recursively. + // Use ObjToIntMap instead of java.util.HashSet for JDK 1.1 compatibility + ObjToIntMap iterating; + + Object interpreterSecurityDomain; + + int version; + + private SecurityController securityController; + private ClassShutter classShutter; + private ErrorReporter errorReporter; + RegExpProxy regExpProxy; + private Locale locale; + private boolean generatingDebug; + private boolean generatingDebugChanged; + private boolean generatingSource=true; + boolean compileFunctionsWithDynamicScopeFlag; + boolean useDynamicScope; + private int optimizationLevel; + private int maximumInterpreterStackDepth; + private WrapFactory wrapFactory; + Debugger debugger; + private Object debuggerData; + private int enterCount; + private Object propertyListeners; + private Hashtable hashtable; + private ClassLoader applicationClassLoader; + + /** + * This is the list of names of objects forcing the creation of + * function activation records. + */ + Hashtable activationNames; + + // For the interpreter to store the last frame for error reports etc. + Object lastInterpreterFrame; + + // For the interpreter to store information about previous invocations + // interpreter invocations + ObjArray previousInterpreterInvocations; + + // For instruction counting (interpreter only) + int instructionCount; + int instructionThreshold; + + // It can be used to return the second index-like result from function + int scratchIndex; + + // It can be used to return the second uint32 result from function + long scratchUint32; + + // It can be used to return the second Scriptable result from function + Scriptable scratchScriptable; + + // Generate an observer count on compiled code + public boolean generateObserverCount = false; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextAction.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextAction.java new file mode 100644 index 0000000..1c584a9 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextAction.java @@ -0,0 +1,59 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Interface to represent arbitrary action that requires to have Context + * object associated with the current thread for its execution. + */ +public interface ContextAction +{ + /** + * Execute action using the supplied Context instance. + * When Rhino runtime calls the method, <tt>cx</tt> will be associated + * with the current thread as active context. + * + * @see Context#call(ContextAction) + * @see ContextFactory#call(ContextAction) + */ + public Object run(Context cx); +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextFactory.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextFactory.java new file mode 100644 index 0000000..4f9fde2 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextFactory.java @@ -0,0 +1,594 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Factory class that Rhino runtime uses to create new {@link Context} + * instances. A <code>ContextFactory</code> can also notify listeners + * about context creation and release. + * <p> + * When the Rhino runtime needs to create new {@link Context} instance during + * execution of {@link Context#enter()} or {@link Context}, it will call + * {@link #makeContext()} of the current global ContextFactory. + * See {@link #getGlobal()} and {@link #initGlobal(ContextFactory)}. + * <p> + * It is also possible to use explicit ContextFactory instances for Context + * creation. This is useful to have a set of independent Rhino runtime + * instances under single JVM. See {@link #call(ContextAction)}. + * <p> + * The following example demonstrates Context customization to terminate + * scripts running more then 10 seconds and to provide better compatibility + * with JavaScript code using MSIE-specific features. + * <pre> + * import org.mozilla.javascript.*; + * + * class MyFactory extends ContextFactory + * { + * + * // Custom {@link Context} to store execution time. + * private static class MyContext extends Context + * { + * long startTime; + * } + * + * static { + * // Initialize GlobalFactory with custom factory + * ContextFactory.initGlobal(new MyFactory()); + * } + * + * // Override {@link #makeContext()} + * protected Context makeContext() + * { + * MyContext cx = new MyContext(); + * // Use pure interpreter mode to allow for + * // {@link #observeInstructionCount(Context, int)} to work + * cx.setOptimizationLevel(-1); + * // Make Rhino runtime to call observeInstructionCount + * // each 10000 bytecode instructions + * cx.setInstructionObserverThreshold(10000); + * return cx; + * } + * + * // Override {@link #hasFeature(Context, int)} + * public boolean hasFeature(Context cx, int featureIndex) + * { + * // Turn on maximum compatibility with MSIE scripts + * switch (featureIndex) { + * case {@link Context#FEATURE_NON_ECMA_GET_YEAR}: + * return true; + * + * case {@link Context#FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME}: + * return true; + * + * case {@link Context#FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER}: + * return true; + * + * case {@link Context#FEATURE_PARENT_PROTO_PROPERTIES}: + * return false; + * } + * return super.hasFeature(cx, featureIndex); + * } + * + * // Override {@link #observeInstructionCount(Context, int)} + * protected void observeInstructionCount(Context cx, int instructionCount) + * { + * MyContext mcx = (MyContext)cx; + * long currentTime = System.currentTimeMillis(); + * if (currentTime - mcx.startTime > 10*1000) { + * // More then 10 seconds from Context creation time: + * // it is time to stop the script. + * // Throw Error instance to ensure that script will never + * // get control back through catch or finally. + * throw new Error(); + * } + * } + * + * // Override {@link #doTopCall(Callable, + Context, Scriptable, + Scriptable, Object[])} + * protected Object doTopCall(Callable callable, + * Context cx, Scriptable scope, + * Scriptable thisObj, Object[] args) + * { + * MyContext mcx = (MyContext)cx; + * mcx.startTime = System.currentTimeMillis(); + * + * return super.doTopCall(callable, cx, scope, thisObj, args); + * } + * + * } + * + * </pre> + */ + +public class ContextFactory +{ + private static volatile boolean hasCustomGlobal; + private static ContextFactory global = new ContextFactory(); + + private volatile boolean sealed; + + private final Object listenersLock = new Object(); + private volatile Object listeners; + private boolean disabledListening; + private ClassLoader applicationClassLoader; + + /** + * Listener of {@link Context} creation and release events. + */ + public interface Listener + { + /** + * Notify about newly created {@link Context} object. + */ + public void contextCreated(Context cx); + + /** + * Notify that the specified {@link Context} instance is no longer + * associated with the current thread. + */ + public void contextReleased(Context cx); + } + + /** + * Get global ContextFactory. + * + * @see #hasExplicitGlobal() + * @see #initGlobal(ContextFactory) + */ + public static ContextFactory getGlobal() + { + return global; + } + + /** + * Check if global factory was set. + * Return true to indicate that {@link #initGlobal(ContextFactory)} was + * already called and false to indicate that the global factory was not + * explicitly set. + * + * @see #getGlobal() + * @see #initGlobal(ContextFactory) + */ + public static boolean hasExplicitGlobal() + { + return hasCustomGlobal; + } + + /** + * Set global ContextFactory. + * The method can only be called once. + * + * @see #getGlobal() + * @see #hasExplicitGlobal() + */ + public synchronized static void initGlobal(ContextFactory factory) + { + if (factory == null) { + throw new IllegalArgumentException(); + } + if (hasCustomGlobal) { + throw new IllegalStateException(); + } + hasCustomGlobal = true; + global = factory; + } + + /** + * Create new {@link Context} instance to be associated with the current + * thread. + * This is a callback method used by Rhino to create {@link Context} + * instance when it is necessary to associate one with the current + * execution thread. <tt>makeContext()</tt> is allowed to call + * {@link Context#seal(Object)} on the result to prevent + * {@link Context} changes by hostile scripts or applets. + */ + protected Context makeContext() + { + return new Context(this); + } + + /** + * Implementation of {@link Context#hasFeature(int featureIndex)}. + * This can be used to customize {@link Context} without introducing + * additional subclasses. + */ + protected boolean hasFeature(Context cx, int featureIndex) + { + int version; + switch (featureIndex) { + case Context.FEATURE_NON_ECMA_GET_YEAR: + /* + * During the great date rewrite of 1.3, we tried to track the + * evolving ECMA standard, which then had a definition of + * getYear which always subtracted 1900. Which we + * implemented, not realizing that it was incompatible with + * the old behavior... now, rather than thrash the behavior + * yet again, we've decided to leave it with the - 1900 + * behavior and point people to the getFullYear method. But + * we try to protect existing scripts that have specified a + * version... + */ + version = cx.getLanguageVersion(); + return (version == Context.VERSION_1_0 + || version == Context.VERSION_1_1 + || version == Context.VERSION_1_2); + + case Context.FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME: + return false; + + case Context.FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER: + return false; + + case Context.FEATURE_TO_STRING_AS_SOURCE: + version = cx.getLanguageVersion(); + return version == Context.VERSION_1_2; + + case Context.FEATURE_PARENT_PROTO_PROPERTIES: + return true; + + case Context.FEATURE_E4X: + version = cx.getLanguageVersion(); + return (version == Context.VERSION_DEFAULT + || version >= Context.VERSION_1_6); + + case Context.FEATURE_DYNAMIC_SCOPE: + return false; + + case Context.FEATURE_STRICT_VARS: + return false; + + case Context.FEATURE_STRICT_EVAL: + return false; + + case Context.FEATURE_LOCATION_INFORMATION_IN_ERROR: + return false; + + case Context.FEATURE_STRICT_MODE: + return false; + + case Context.FEATURE_WARNING_AS_ERROR: + return false; + + case Context.FEATURE_ENHANCED_JAVA_ACCESS: + return false; + } + // It is a bug to call the method with unknown featureIndex + throw new IllegalArgumentException(String.valueOf(featureIndex)); + } + + private boolean isDom3Present() { + Class nodeClass = Kit.classOrNull("org.w3c.dom.Node"); + if (nodeClass == null) return false; + // Check to see whether DOM3 is present; use a new method defined in + // DOM3 that is vital to our implementation + try { + nodeClass.getMethod("getUserData", new Class[] { String.class }); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + + /** + * Provides a default + * {@link org.mozilla.javascript.xml.XMLLib.Factory XMLLib.Factory} + * to be used by the <code>Context</code> instances produced by this + * factory. See {@link Context#getE4xImplementationFactory} for details. + * + * May return null, in which case E4X functionality is not supported in + * Rhino. + * + * The default implementation now prefers the DOM3 E4X implementation. + */ + protected org.mozilla.javascript.xml.XMLLib.Factory + getE4xImplementationFactory() + { + // Must provide default implementation, rather than abstract method, + // so that past implementors of ContextFactory do not fail at runtime + // upon invocation of this method. + // Note that the default implementation returns null if we + // neither have XMLBeans nor a DOM3 implementation present. + + if (isDom3Present()) { + return org.mozilla.javascript.xml.XMLLib.Factory.create( + "org.mozilla.javascript.xmlimpl.XMLLibImpl" + ); + } else if (Kit.classOrNull("org.apache.xmlbeans.XmlCursor") != null) { + return org.mozilla.javascript.xml.XMLLib.Factory.create( + "org.mozilla.javascript.xml.impl.xmlbeans.XMLLibImpl" + ); + } else { + return null; + } + } + + + /** + * Create class loader for generated classes. + * This method creates an instance of the default implementation + * of {@link GeneratedClassLoader}. Rhino uses this interface to load + * generated JVM classes when no {@link SecurityController} + * is installed. + * Application can override the method to provide custom class loading. + */ + protected GeneratedClassLoader createClassLoader(ClassLoader parent) + { + return new DefiningClassLoader(parent); + } + + /** + * Get ClassLoader to use when searching for Java classes. + * Unless it was explicitly initialized with + * {@link #initApplicationClassLoader(ClassLoader)} the method returns + * null to indicate that Thread.getContextClassLoader() should be used. + */ + public final ClassLoader getApplicationClassLoader() + { + return applicationClassLoader; + } + + /** + * Set explicit class loader to use when searching for Java classes. + * + * @see #getApplicationClassLoader() + */ + public final void initApplicationClassLoader(ClassLoader loader) + { + if (loader == null) + throw new IllegalArgumentException("loader is null"); + if (!Kit.testIfCanLoadRhinoClasses(loader)) + throw new IllegalArgumentException( + "Loader can not resolve Rhino classes"); + + if (this.applicationClassLoader != null) + throw new IllegalStateException( + "applicationClassLoader can only be set once"); + checkNotSealed(); + + this.applicationClassLoader = loader; + } + + /** + * Execute top call to script or function. + * When the runtime is about to execute a script or function that will + * create the first stack frame with scriptable code, it calls this method + * to perform the real call. In this way execution of any script + * happens inside this function. + */ + protected Object doTopCall(Callable callable, + Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + return callable.call(cx, scope, thisObj, args); + } + + /** + * Implementation of + * {@link Context#observeInstructionCount(int instructionCount)}. + * This can be used to customize {@link Context} without introducing + * additional subclasses. + */ + protected void observeInstructionCount(Context cx, int instructionCount) + { + } + + protected void onContextCreated(Context cx) + { + Object listeners = this.listeners; + for (int i = 0; ; ++i) { + Listener l = (Listener)Kit.getListener(listeners, i); + if (l == null) + break; + l.contextCreated(cx); + } + } + + protected void onContextReleased(Context cx) + { + Object listeners = this.listeners; + for (int i = 0; ; ++i) { + Listener l = (Listener)Kit.getListener(listeners, i); + if (l == null) + break; + l.contextReleased(cx); + } + } + + public final void addListener(Listener listener) + { + checkNotSealed(); + synchronized (listenersLock) { + if (disabledListening) { + throw new IllegalStateException(); + } + listeners = Kit.addListener(listeners, listener); + } + } + + public final void removeListener(Listener listener) + { + checkNotSealed(); + synchronized (listenersLock) { + if (disabledListening) { + throw new IllegalStateException(); + } + listeners = Kit.removeListener(listeners, listener); + } + } + + /** + * The method is used only to implement + * Context.disableStaticContextListening() + */ + final void disableContextListening() + { + checkNotSealed(); + synchronized (listenersLock) { + disabledListening = true; + listeners = null; + } + } + + /** + * Checks if this is a sealed ContextFactory. + * @see #seal() + */ + public final boolean isSealed() + { + return sealed; + } + + /** + * Seal this ContextFactory so any attempt to modify it like to add or + * remove its listeners will throw an exception. + * @see #isSealed() + */ + public final void seal() + { + checkNotSealed(); + sealed = true; + } + + protected final void checkNotSealed() + { + if (sealed) throw new IllegalStateException(); + } + + /** + * Call {@link ContextAction#run(Context cx)} + * using the {@link Context} instance associated with the current thread. + * If no Context is associated with the thread, then + * {@link #makeContext()} will be called to construct + * new Context instance. The instance will be temporary associated + * with the thread during call to {@link ContextAction#run(Context)}. + * + * @see ContextFactory#call(ContextAction) + * @see Context#call(ContextFactory factory, Callable callable, + * Scriptable scope, Scriptable thisObj, + * Object[] args) + */ + public final Object call(ContextAction action) + { + return Context.call(this, action); + } + + /** + * Get a context associated with the current thread, creating one if need + * be. The Context stores the execution state of the JavaScript engine, so + * it is required that the context be entered before execution may begin. + * Once a thread has entered a Context, then getCurrentContext() may be + * called to find the context that is associated with the current thread. + * <p> + * Calling <code>enterContext()</code> will return either the Context + * currently associated with the thread, or will create a new context and + * associate it with the current thread. Each call to + * <code>enterContext()</code> must have a matching call to + * {@link Context#exit()}. + * <pre> + * Context cx = contextFactory.enterContext(); + * try { + * ... + * cx.evaluateString(...); + * } finally { + * Context.exit(); + * } + * </pre> + * Instead of using <tt>enterContext()</tt>, <tt>exit()</tt> pair consider + * using {@link #call(ContextAction)} which guarantees proper association + * of Context instances with the current thread. + * With this method the above example becomes: + * <pre> + * ContextFactory.call(new ContextAction() { + * public Object run(Context cx) { + * ... + * cx.evaluateString(...); + * return null; + * } + * }); + * </pre> + * @return a Context associated with the current thread + * @see Context#getCurrentContext() + * @see Context#exit() + * @see #call(ContextAction) + */ + public Context enterContext() + { + return enterContext(null); + } + + /** + * @deprecated use {@link #enterContext()} instead + * @return a Context associated with the current thread + */ + public final Context enter() + { + return enterContext(null); + } + + /** + * @deprecated Use {@link Context#exit()} instead. + */ + public final void exit() + { + Context.exit(); + } + + /** + * Get a Context associated with the current thread, using the given + * Context if need be. + * <p> + * The same as <code>enterContext()</code> except that <code>cx</code> + * is associated with the current thread and returned if the current thread + * has no associated context and <code>cx</code> is not associated with any + * other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @see #enterContext() + * @see #call(ContextAction) + * @throws IllegalStateException if <code>cx</code> is already associated + * with a different thread + */ + public final Context enterContext(Context cx) + { + return Context.enter(cx, this); + } +}
\ No newline at end of file diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextListener.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextListener.java new file mode 100644 index 0000000..5e17145 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ContextListener.java @@ -0,0 +1,60 @@ +/* -*- 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): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * @deprecated Embeddings that wish to customize newly created + * {@link Context} instances should implement + * {@link ContextFactory.Listener}. + */ +public interface ContextListener extends ContextFactory.Listener +{ + + /** + * @deprecated Rhino runtime never calls the method. + */ + public void contextEntered(Context cx); + + /** + * @deprecated Rhino runtime never calls the method. + */ + public void contextExited(Context cx); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DToA.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DToA.java new file mode 100644 index 0000000..ad2a68a --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DToA.java @@ -0,0 +1,1271 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Waldemar Horwat + * Roger Lawrence + * Attila Szegedi + * + * 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 ***** */ + +/**************************************************************** + * + * The author of this software is David M. Gay. + * + * Copyright (c) 1991, 2000, 2001 by Lucent Technologies. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose without fee is hereby granted, provided that this entire notice + * is included in all copies of any software which is or includes a copy + * or modification of this software and in all copies of the supporting + * documentation for such software. + * + * THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED + * WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY + * REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY + * OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. + * + ***************************************************************/ + +package org.mozilla.javascript; + +import java.math.BigInteger; + +class DToA { + + +/* "-0.0000...(1073 zeros after decimal point)...0001\0" is the longest string that we could produce, + * which occurs when printing -5e-324 in binary. We could compute a better estimate of the size of + * the output string and malloc fewer bytes depending on d and base, but why bother? */ + + private static final int DTOBASESTR_BUFFER_SIZE = 1078; + + private static char BASEDIGIT(int digit) { + return (char)((digit >= 10) ? 'a' - 10 + digit : '0' + digit); + } + + static final int + DTOSTR_STANDARD = 0, /* Either fixed or exponential format; round-trip */ + DTOSTR_STANDARD_EXPONENTIAL = 1, /* Always exponential format; round-trip */ + DTOSTR_FIXED = 2, /* Round to <precision> digits after the decimal point; exponential if number is large */ + DTOSTR_EXPONENTIAL = 3, /* Always exponential format; <precision> significant digits */ + DTOSTR_PRECISION = 4; /* Either fixed or exponential format; <precision> significant digits */ + + + private static final int Frac_mask = 0xfffff; + private static final int Exp_shift = 20; + private static final int Exp_msk1 = 0x100000; + + private static final long Frac_maskL = 0xfffffffffffffL; + private static final int Exp_shiftL = 52; + private static final long Exp_msk1L = 0x10000000000000L; + + private static final int Bias = 1023; + private static final int P = 53; + + private static final int Exp_shift1 = 20; + private static final int Exp_mask = 0x7ff00000; + private static final int Exp_mask_shifted = 0x7ff; + private static final int Bndry_mask = 0xfffff; + private static final int Log2P = 1; + + private static final int Sign_bit = 0x80000000; + private static final int Exp_11 = 0x3ff00000; + private static final int Ten_pmax = 22; + private static final int Quick_max = 14; + private static final int Bletch = 0x10; + private static final int Frac_mask1 = 0xfffff; + private static final int Int_max = 14; + private static final int n_bigtens = 5; + + + private static final double tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 + }; + + private static final double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; + + private static int lo0bits(int y) + { + int k; + int x = y; + + if ((x & 7) != 0) { + if ((x & 1) != 0) + return 0; + if ((x & 2) != 0) { + return 1; + } + return 2; + } + k = 0; + if ((x & 0xffff) == 0) { + k = 16; + x >>>= 16; + } + if ((x & 0xff) == 0) { + k += 8; + x >>>= 8; + } + if ((x & 0xf) == 0) { + k += 4; + x >>>= 4; + } + if ((x & 0x3) == 0) { + k += 2; + x >>>= 2; + } + if ((x & 1) == 0) { + k++; + x >>>= 1; + if ((x & 1) == 0) + return 32; + } + return k; + } + + /* Return the number (0 through 32) of most significant zero bits in x. */ + private static int hi0bits(int x) + { + int k = 0; + + if ((x & 0xffff0000) == 0) { + k = 16; + x <<= 16; + } + if ((x & 0xff000000) == 0) { + k += 8; + x <<= 8; + } + if ((x & 0xf0000000) == 0) { + k += 4; + x <<= 4; + } + if ((x & 0xc0000000) == 0) { + k += 2; + x <<= 2; + } + if ((x & 0x80000000) == 0) { + k++; + if ((x & 0x40000000) == 0) + return 32; + } + return k; + } + + private static void stuffBits(byte bits[], int offset, int val) + { + bits[offset] = (byte)(val >> 24); + bits[offset + 1] = (byte)(val >> 16); + bits[offset + 2] = (byte)(val >> 8); + bits[offset + 3] = (byte)(val); + } + + /* Convert d into the form b*2^e, where b is an odd integer. b is the returned + * Bigint and e is the returned binary exponent. Return the number of significant + * bits in b in bits. d must be finite and nonzero. */ + private static BigInteger d2b(double d, int[] e, int[] bits) + { + byte dbl_bits[]; + int i, k, y, z, de; + long dBits = Double.doubleToLongBits(d); + int d0 = (int)(dBits >>> 32); + int d1 = (int)(dBits); + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ + + if ((de = (d0 >>> Exp_shift)) != 0) + z |= Exp_msk1; + + if ((y = d1) != 0) { + dbl_bits = new byte[8]; + k = lo0bits(y); + y >>>= k; + if (k != 0) { + stuffBits(dbl_bits, 4, y | z << (32 - k)); + z >>= k; + } + else + stuffBits(dbl_bits, 4, y); + stuffBits(dbl_bits, 0, z); + i = (z != 0) ? 2 : 1; + } + else { + // JS_ASSERT(z); + dbl_bits = new byte[4]; + k = lo0bits(z); + z >>>= k; + stuffBits(dbl_bits, 0, z); + k += 32; + i = 1; + } + if (de != 0) { + e[0] = de - Bias - (P-1) + k; + bits[0] = P - k; + } + else { + e[0] = de - Bias - (P-1) + 1 + k; + bits[0] = 32*i - hi0bits(z); + } + return new BigInteger(dbl_bits); + } + + static String JS_dtobasestr(int base, double d) + { + if (!(2 <= base && base <= 36)) + throw new IllegalArgumentException("Bad base: "+base); + + /* Check for Infinity and NaN */ + if (Double.isNaN(d)) { + return "NaN"; + } else if (Double.isInfinite(d)) { + return (d > 0.0) ? "Infinity" : "-Infinity"; + } else if (d == 0) { + // ALERT: should it distinguish -0.0 from +0.0 ? + return "0"; + } + + boolean negative; + if (d >= 0.0) { + negative = false; + } else { + negative = true; + d = -d; + } + + /* Get the integer part of d including '-' sign. */ + String intDigits; + + double dfloor = Math.floor(d); + long lfloor = (long)dfloor; + if (lfloor == dfloor) { + // int part fits long + intDigits = Long.toString((negative) ? -lfloor : lfloor, base); + } else { + // BigInteger should be used + long floorBits = Double.doubleToLongBits(dfloor); + int exp = (int)(floorBits >> Exp_shiftL) & Exp_mask_shifted; + long mantissa; + if (exp == 0) { + mantissa = (floorBits & Frac_maskL) << 1; + } else { + mantissa = (floorBits & Frac_maskL) | Exp_msk1L; + } + if (negative) { + mantissa = -mantissa; + } + exp -= 1075; + BigInteger x = BigInteger.valueOf(mantissa); + if (exp > 0) { + x = x.shiftLeft(exp); + } else if (exp < 0) { + x = x.shiftRight(-exp); + } + intDigits = x.toString(base); + } + + if (d == dfloor) { + // No fraction part + return intDigits; + } else { + /* We have a fraction. */ + + char[] buffer; /* The output string */ + int p; /* index to current position in the buffer */ + int digit; + double df; /* The fractional part of d */ + BigInteger b; + + buffer = new char[DTOBASESTR_BUFFER_SIZE]; + p = 0; + df = d - dfloor; + + long dBits = Double.doubleToLongBits(d); + int word0 = (int)(dBits >> 32); + int word1 = (int)(dBits); + + int[] e = new int[1]; + int[] bbits = new int[1]; + + b = d2b(df, e, bbits); +// JS_ASSERT(e < 0); + /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */ + + int s2 = -(word0 >>> Exp_shift1 & Exp_mask >> Exp_shift1); + if (s2 == 0) + s2 = -1; + s2 += Bias + P; + /* 1/2^s2 = (nextDouble(d) - d)/2 */ +// JS_ASSERT(-s2 < e); + BigInteger mlo = BigInteger.valueOf(1); + BigInteger mhi = mlo; + if ((word1 == 0) && ((word0 & Bndry_mask) == 0) + && ((word0 & (Exp_mask & Exp_mask << 1)) != 0)) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the output string's value is less than d. */ + s2 += Log2P; + mhi = BigInteger.valueOf(1<<Log2P); + } + + b = b.shiftLeft(e[0] + s2); + BigInteger s = BigInteger.valueOf(1); + s = s.shiftLeft(s2); + /* At this point we have the following: + * s = 2^s2; + * 1 > df = b/2^s2 > 0; + * (d - prevDouble(d))/2 = mlo/2^s2; + * (nextDouble(d) - d)/2 = mhi/2^s2. */ + BigInteger bigBase = BigInteger.valueOf(base); + + boolean done = false; + do { + b = b.multiply(bigBase); + BigInteger[] divResult = b.divideAndRemainder(s); + b = divResult[1]; + digit = (char)(divResult[0].intValue()); + if (mlo == mhi) + mlo = mhi = mlo.multiply(bigBase); + else { + mlo = mlo.multiply(bigBase); + mhi = mhi.multiply(bigBase); + } + + /* Do we yet have the shortest string that will round to d? */ + int j = b.compareTo(mlo); + /* j is b/2^s2 compared with mlo/2^s2. */ + BigInteger delta = s.subtract(mhi); + int j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */ + if (j1 == 0 && ((word1 & 1) == 0)) { + if (j > 0) + digit++; + done = true; + } else + if (j < 0 || (j == 0 && ((word1 & 1) == 0))) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant digit. + Use whichever would produce an output value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(s); + if (j1 > 0) /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output + * such as 3.5 in base 3. */ + digit++; + } + done = true; + } else if (j1 > 0) { + digit++; + done = true; + } +// JS_ASSERT(digit < (uint32)base); + buffer[p++] = BASEDIGIT(digit); + } while (!done); + + StringBuffer sb = new StringBuffer(intDigits.length() + 1 + p); + sb.append(intDigits); + sb.append('.'); + sb.append(buffer, 0, p); + return sb.toString(); + } + + } + + /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + + static int word0(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits >> 32); + } + + static double setWord0(double d, int i) + { + long dBits = Double.doubleToLongBits(d); + dBits = ((long)i << 32) | (dBits & 0x0FFFFFFFFL); + return Double.longBitsToDouble(dBits); + } + + static int word1(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits); + } + + /* Return b * 5^k. k must be nonnegative. */ + // XXXX the C version built a cache of these + static BigInteger pow5mult(BigInteger b, int k) + { + return b.multiply(BigInteger.valueOf(5).pow(k)); + } + + static boolean roundOff(StringBuffer buf) + { + int i = buf.length(); + while (i != 0) { + --i; + char c = buf.charAt(i); + if (c != '9') { + buf.setCharAt(i, (char)(c + 1)); + buf.setLength(i + 1); + return false; + } + } + buf.setLength(0); + return true; + } + + /* Always emits at least one digit. */ + /* If biasUp is set, then rounding in modes 2 and 3 will round away from zero + * when the number is exactly halfway between two representable values. For example, + * rounding 2.5 to zero digits after the decimal point will return 3 and not 2. + * 2.49 will still round to 2, and 2.51 will still round to 3. */ + /* bufsize should be at least 20 for modes 0 and 1. For the other modes, + * bufsize should be two greater than the maximum number of output characters expected. */ + static int + JS_dtoa(double d, int mode, boolean biasUp, int ndigits, + boolean[] sign, StringBuffer buf) + { + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4-9 should give the same return values as 2-3, i.e., + 4 <= mode <= 9 ==> same return as mode + 2 + (mode & 1). These modes are mainly for + debugging; often they run slower but sometimes + faster than modes 2-3. + 4,5,8,9 ==> left-to-right digit generation. + 6-9 ==> don't try fast floating-point estimate + (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int b2, b5, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, m2, m5, s2, s5; + char dig; + long L; + long x; + BigInteger b, b1, delta, mlo, mhi, S; + int[] be = new int[1]; + int[] bbits = new int[1]; + double d2, ds, eps; + boolean spec_case, denorm, k_check, try_quick, leftright; + + if ((word0(d) & Sign_bit) != 0) { + /* set sign for everything, including 0's and NaNs */ + sign[0] = true; + // word0(d) &= ~Sign_bit; /* clear sign bit */ + d = setWord0(d, word0(d) & ~Sign_bit); + } + else + sign[0] = false; + + if ((word0(d) & Exp_mask) == Exp_mask) { + /* Infinity or NaN */ + buf.append(((word1(d) == 0) && ((word0(d) & Frac_mask) == 0)) ? "Infinity" : "NaN"); + return 9999; + } + if (d == 0) { +// no_digits: + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + + b = d2b(d, be, bbits); + if ((i = (word0(d) >>> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { + d2 = setWord0(d, (word0(d) & Frac_mask1) | Exp_11); + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + i -= Bias; + denorm = false; + } + else { + /* d is denormalized */ + i = bbits[0] + be[0] + (Bias + (P-1) - 1); + x = (i > 32) ? word0(d) << (64 - i) | word1(d) >>> (i - 32) : word1(d) << (32 - i); +// d2 = x; +// word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + d2 = setWord0(x, word0(x) - 31*Exp_msk1); + i -= (Bias + (P-1) - 1) + 1; + denorm = true; + } + /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */ + ds = (d2-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0.0 && ds != k) + k--; /* want k = floor(ds) */ + k_check = true; + if (k >= 0 && k <= Ten_pmax) { + if (d < tens[k]) + k--; + k_check = false; + } + /* At this point floor(log10(d)) <= k <= floor(log10(d))+1. + If k_check is zero, we're guaranteed that k = floor(log10(d)). */ + j = bbits[0] - i - 1; + /* At this point d = b/2^j, where b is an odd integer. */ + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer, + b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */ + if (mode < 0 || mode > 9) + mode = 0; + try_quick = true; + if (mode > 5) { + mode -= 4; + try_quick = false; + } + leftright = true; + ilim = ilim1 = 0; + switch(mode) { + case 0: + case 1: + ilim = ilim1 = -1; + i = 18; + ndigits = 0; + break; + case 2: + leftright = false; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = false; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + /* ilim is the maximum number of significant digits we want, based on k and ndigits. */ + /* ilim1 is the maximum number of significant digits we want, based on k and ndigits, + when it turns out that k was computed too high by one. */ + + boolean fast_failed = false; + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + d2 = d; + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if ((j & Bletch) != 0) { + /* prevent overflows */ + j &= Bletch - 1; + d /= bigtens[n_bigtens-1]; + ieps++; + } + for(; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + ds *= bigtens[i]; + } + d /= ds; + } + else if ((j1 = -k) != 0) { + d *= tens[j1 & 0xf]; + for(j = j1 >> 4; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + d *= bigtens[i]; + } + } + /* Check that k was computed correctly. */ + if (k_check && d < 1.0 && ilim > 0) { + if (ilim1 <= 0) + fast_failed = true; + else { + ilim = ilim1; + k--; + d *= 10.; + ieps++; + } + } + /* eps bounds the cumulative error. */ +// eps = ieps*d + 7.0; +// word0(eps) -= (P-1)*Exp_msk1; + eps = ieps*d + 7.0; + eps = setWord0(eps, word0(eps) - (P-1)*Exp_msk1); + if (ilim == 0) { + S = mhi = null; + d -= 5.0; + if (d > eps) { + buf.append('1'); + k++; + return k + 1; + } + if (d < -eps) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + fast_failed = true; + } + if (!fast_failed) { + fast_failed = true; + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + eps = 0.5/tens[ilim-1] - eps; + for(i = 0;;) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (d < eps) { + return k + 1; + } + if (1.0 - d < eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + if (++i >= ilim) + break; + eps *= 10.0; + d *= 10.0; + } + } + else { + /* Generate ilim digits, then fix them up. */ + eps *= tens[ilim-1]; + for(i = 1;; i++, d *= 10.0) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (i == ilim) { + if (d > 0.5 + eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + else + if (d < 0.5 - eps) { + stripTrailingZeroes(buf); +// while(*--s == '0') ; +// s++; + return k + 1; + } + break; + } + } + } + } + if (fast_failed) { + buf.setLength(0); + d = d2; + k = k0; + ilim = ilim0; + } + } + + /* Do we have a "small" integer? */ + + if (be[0] >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = null; + if (ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds)) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + buf.append('1'); + k++; + return k + 1; + } + for(i = 1;; i++) { + L = (long) (d / ds); + d -= L*ds; + buf.append((char)('0' + L)); + if (i == ilim) { + d += d; + if ((d > ds) || (d == ds && (((L & 1) != 0) || biasUp))) { +// bump_up: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s = '0'; +// break; +// } +// ++*s++; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + } + break; + } + d *= 10.0; + if (d == 0) + break; + } + return k + 1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = null; + if (leftright) { + if (mode < 2) { + i = (denorm) ? be[0] + (Bias + (P-1) - 1 + 1) : 1 + P - bbits[0]; + /* i is 1 plus the number of trailing zero bits in d's significand. Thus, + (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */ + } + else { + j = ilim - 1; + if (m5 >= j) + m5 -= j; + else { + s5 += j -= m5; + b5 += j; + m5 = 0; + } + if ((i = ilim) < 0) { + m2 -= i; + i = 0; + } + /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */ + } + b2 += i; + s2 += i; + mhi = BigInteger.valueOf(1); + /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or + input (when mode < 2) significant digit, divided by 10^k. */ + } + /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in + b2, m2, and s2 without changing the equalities. */ + if (m2 > 0 && s2 > 0) { + i = (m2 < s2) ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + + /* Fold b5 into b and m5 into mhi. */ + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mhi.multiply(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and + (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */ + + S = BigInteger.valueOf(1); + if (s5 > 0) + S = pow5mult(S, s5); + /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and + (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */ + + /* Check for special case that d is a normalized power of 2. */ + spec_case = false; + if (mode < 2) { + if ( (word1(d) == 0) && ((word0(d) & Bndry_mask) == 0) + && ((word0(d) & (Exp_mask & Exp_mask << 1)) != 0) + ) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the decimal output string's value is less than d. */ + b2 += Log2P; + s2 += Log2P; + spec_case = true; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + byte [] S_bytes = S.toByteArray(); + int S_hiWord = 0; + for (int idx = 0; idx < 4; idx++) { + S_hiWord = (S_hiWord << 8); + if (idx < S_bytes.length) + S_hiWord |= (S_bytes[idx] & 0xFF); + } + if ((i = (((s5 != 0) ? 32 - hi0bits(S_hiWord) : 1) + s2) & 0x1f) != 0) + i = 32 - i; + /* i is the number of leading zero bits in the most significant word of S*2^s2. */ + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */ + if (b2 > 0) + b = b.shiftLeft(b2); + if (s2 > 0) + S = S.shiftLeft(s2); + /* Now we have d/10^k = b/S and + (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */ + if (k_check) { + if (b.compareTo(S) < 0) { + k--; + b = b.multiply(BigInteger.valueOf(10)); /* we botched the k estimate */ + if (leftright) + mhi = mhi.multiply(BigInteger.valueOf(10)); + ilim = ilim1; + } + } + /* At this point 1 <= d/10^k = b/S < 10. */ + + if (ilim <= 0 && mode > 2) { + /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode. + Output either zero or the minimum nonzero output depending on which is closer to d. */ + if ((ilim < 0 ) + || ((i = b.compareTo(S = S.multiply(BigInteger.valueOf(5)))) < 0) + || ((i == 0 && !biasUp))) { + /* Always emit at least one digit. If the number appears to be zero + using the current mode, then emit one '0' digit and set decpt to 1. */ + /*no_digits: + k = -1 - ndigits; + goto ret; */ + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; +// goto no_digits; + } +// one_digit: + buf.append('1'); + k++; + return k + 1; + } + if (leftright) { + if (m2 > 0) + mhi = mhi.shiftLeft(m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = mlo; + mhi = mhi.shiftLeft(Log2P); + } + /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */ + /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */ + + for(i = 1;;i++) { + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = b.compareTo(mlo); + /* j is b/S compared with mlo/S. */ + delta = S.subtract(mhi); + j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/S compared with 1 - mhi/S. */ + if ((j1 == 0) && (mode == 0) && ((word1(d) & 1) == 0)) { + if (dig == '9') { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + if (j > 0) + dig++; + buf.append(dig); + return k + 1; + } + if ((j < 0) + || ((j == 0) + && (mode == 0) + && ((word1(d) & 1) == 0) + )) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant decimal digit. + Use whichever would produce a decimal value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(S); + if (((j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp))) + && (dig++ == '9')) { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + } + buf.append(dig); + return k + 1; + } + if (j1 > 0) { + if (dig == '9') { /* possible if i == 1 */ +// round_9_up: +// *s++ = '9'; +// goto roundoff; + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; + } + buf.append((char)(dig + 1)); + return k + 1; + } + buf.append(dig); + if (i == ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + if (mlo == mhi) + mlo = mhi = mhi.multiply(BigInteger.valueOf(10)); + else { + mlo = mlo.multiply(BigInteger.valueOf(10)); + mhi = mhi.multiply(BigInteger.valueOf(10)); + } + } + } + else + for(i = 1;; i++) { +// (char)(dig = quorem(b,S) + '0'); + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + buf.append(dig); + if (i >= ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + } + + /* Round off last digit */ + + b = b.shiftLeft(1); + j = b.compareTo(S); + if ((j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp))) { +// roundoff: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s++ = '1'; +// goto ret; +// } +// ++*s++; + if (roundOff(buf)) { + k++; + buf.append('1'); + return k + 1; + } + } + else { + stripTrailingZeroes(buf); +// while(*--s == '0') ; +// s++; + } +// ret: +// Bfree(S); +// if (mhi) { +// if (mlo && mlo != mhi) +// Bfree(mlo); +// Bfree(mhi); +// } +// ret1: +// Bfree(b); +// JS_ASSERT(s < buf + bufsize); + return k + 1; + } + + private static void + stripTrailingZeroes(StringBuffer buf) + { +// while(*--s == '0') ; +// s++; + int bl = buf.length(); + while(bl-->0 && buf.charAt(bl) == '0') { + // empty + } + buf.setLength(bl + 1); + } + + /* Mapping of JSDToStrMode -> JS_dtoa mode */ + private static final int dtoaModes[] = { + 0, /* DTOSTR_STANDARD */ + 0, /* DTOSTR_STANDARD_EXPONENTIAL, */ + 3, /* DTOSTR_FIXED, */ + 2, /* DTOSTR_EXPONENTIAL, */ + 2}; /* DTOSTR_PRECISION */ + + static void + JS_dtostr(StringBuffer buffer, int mode, int precision, double d) + { + int decPt; /* Position of decimal point relative to first digit returned by JS_dtoa */ + boolean[] sign = new boolean[1]; /* true if the sign bit was set in d */ + int nDigits; /* Number of significand digits returned by JS_dtoa */ + +// JS_ASSERT(bufferSize >= (size_t)(mode <= DTOSTR_STANDARD_EXPONENTIAL ? DTOSTR_STANDARD_BUFFER_SIZE : +// DTOSTR_VARIABLE_BUFFER_SIZE(precision))); + + if (mode == DTOSTR_FIXED && (d >= 1e21 || d <= -1e21)) + mode = DTOSTR_STANDARD; /* Change mode here rather than below because the buffer may not be large enough to hold a large integer. */ + + decPt = JS_dtoa(d, dtoaModes[mode], mode >= DTOSTR_FIXED, precision, sign, buffer); + nDigits = buffer.length(); + + /* If Infinity, -Infinity, or NaN, return the string regardless of the mode. */ + if (decPt != 9999) { + boolean exponentialNotation = false; + int minNDigits = 0; /* Minimum number of significand digits required by mode and precision */ + int p; + + switch (mode) { + case DTOSTR_STANDARD: + if (decPt < -5 || decPt > 21) + exponentialNotation = true; + else + minNDigits = decPt; + break; + + case DTOSTR_FIXED: + if (precision >= 0) + minNDigits = decPt + precision; + else + minNDigits = decPt; + break; + + case DTOSTR_EXPONENTIAL: +// JS_ASSERT(precision > 0); + minNDigits = precision; + /* Fall through */ + case DTOSTR_STANDARD_EXPONENTIAL: + exponentialNotation = true; + break; + + case DTOSTR_PRECISION: +// JS_ASSERT(precision > 0); + minNDigits = precision; + if (decPt < -5 || decPt > precision) + exponentialNotation = true; + break; + } + + /* If the number has fewer than minNDigits, pad it with zeros at the end */ + if (nDigits < minNDigits) { + p = minNDigits; + nDigits = minNDigits; + do { + buffer.append('0'); + } while (buffer.length() != p); + } + + if (exponentialNotation) { + /* Insert a decimal point if more than one significand digit */ + if (nDigits != 1) { + buffer.insert(1, '.'); + } + buffer.append('e'); + if ((decPt - 1) >= 0) + buffer.append('+'); + buffer.append(decPt - 1); +// JS_snprintf(numEnd, bufferSize - (numEnd - buffer), "e%+d", decPt-1); + } else if (decPt != nDigits) { + /* Some kind of a fraction in fixed notation */ +// JS_ASSERT(decPt <= nDigits); + if (decPt > 0) { + /* dd...dd . dd...dd */ + buffer.insert(decPt, '.'); + } else { + /* 0 . 00...00dd...dd */ + for (int i = 0; i < 1 - decPt; i++) + buffer.insert(0, '0'); + buffer.insert(1, '.'); + } + } + } + + /* If negative and neither -0.0 nor NaN, output a leading '-'. */ + if (sign[0] && + !(word0(d) == Sign_bit && word1(d) == 0) && + !((word0(d) & Exp_mask) == Exp_mask && + ((word1(d) != 0) || ((word0(d) & Frac_mask) != 0)))) { + buffer.insert(0, '-'); + } + } + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Decompiler.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Decompiler.java new file mode 100644 index 0000000..8547d37 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Decompiler.java @@ -0,0 +1,918 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mike Ang + * Igor Bukanov + * Bob Jervis + * Mike McCabe + * + * 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; + +/** + * The following class save decompilation information about the source. + * Source information is returned from the parser as a String + * associated with function nodes and with the toplevel script. When + * saved in the constant pool of a class, this string will be UTF-8 + * encoded, and token values will occupy a single byte. + + * Source is saved (mostly) as token numbers. The tokens saved pretty + * much correspond to the token stream of a 'canonical' representation + * of the input program, as directed by the parser. (There were a few + * cases where tokens could have been left out where decompiler could + * easily reconstruct them, but I left them in for clarity). (I also + * looked adding source collection to TokenStream instead, where I + * could have limited the changes to a few lines in getToken... but + * this wouldn't have saved any space in the resulting source + * representation, and would have meant that I'd have to duplicate + * parser logic in the decompiler to disambiguate situations where + * newlines are important.) The function decompile expands the + * tokens back into their string representations, using simple + * lookahead to correct spacing and indentation. + * + * Assignments are saved as two-token pairs (Token.ASSIGN, op). Number tokens + * are stored inline, as a NUMBER token, a character representing the type, and + * either 1 or 4 characters representing the bit-encoding of the number. String + * types NAME, STRING and OBJECT are currently stored as a token type, + * followed by a character giving the length of the string (assumed to + * be less than 2^16), followed by the characters of the string + * inlined into the source string. Changing this to some reference to + * to the string in the compiled class' constant pool would probably + * save a lot of space... but would require some method of deriving + * the final constant pool entry from information available at parse + * time. + */ +public class Decompiler +{ + /** + * Flag to indicate that the decompilation should omit the + * function header and trailing brace. + */ + public static final int ONLY_BODY_FLAG = 1 << 0; + + /** + * Flag to indicate that the decompilation generates toSource result. + */ + public static final int TO_SOURCE_FLAG = 1 << 1; + + /** + * Decompilation property to specify initial ident value. + */ + public static final int INITIAL_INDENT_PROP = 1; + + /** + * Decompilation property to specify default identation offset. + */ + public static final int INDENT_GAP_PROP = 2; + + /** + * Decompilation property to specify identation offset for case labels. + */ + public static final int CASE_GAP_PROP = 3; + + // Marker to denote the last RC of function so it can be distinguished from + // the last RC of object literals in case of function expressions + private static final int FUNCTION_END = Token.LAST_TOKEN + 1; + + String getEncodedSource() + { + return sourceToString(0); + } + + int getCurrentOffset() + { + return sourceTop; + } + + int markFunctionStart(int functionType) + { + int savedOffset = getCurrentOffset(); + addToken(Token.FUNCTION); + append((char)functionType); + return savedOffset; + } + + int markFunctionEnd(int functionStart) + { + int offset = getCurrentOffset(); + append((char)FUNCTION_END); + return offset; + } + + void addToken(int token) + { + if (!(0 <= token && token <= Token.LAST_TOKEN)) + throw new IllegalArgumentException(); + + append((char)token); + } + + void addEOL(int token) + { + if (!(0 <= token && token <= Token.LAST_TOKEN)) + throw new IllegalArgumentException(); + + append((char)token); + append((char)Token.EOL); + } + + void addName(String str) + { + addToken(Token.NAME); + appendString(str); + } + + void addString(String str) + { + addToken(Token.STRING); + appendString(str); + } + + void addRegexp(String regexp, String flags) + { + addToken(Token.REGEXP); + appendString('/' + regexp + '/' + flags); + } + + void addNumber(double n) + { + addToken(Token.NUMBER); + + /* encode the number in the source stream. + * Save as NUMBER type (char | char char char char) + * where type is + * 'D' - double, 'S' - short, 'J' - long. + + * We need to retain float vs. integer type info to keep the + * behavior of liveconnect type-guessing the same after + * decompilation. (Liveconnect tries to present 1.0 to Java + * as a float/double) + * OPT: This is no longer true. We could compress the format. + + * This may not be the most space-efficient encoding; + * the chars created below may take up to 3 bytes in + * constant pool UTF-8 encoding, so a Double could take + * up to 12 bytes. + */ + + long lbits = (long)n; + if (lbits != n) { + // if it's floating point, save as a Double bit pattern. + // (12/15/97 our scanner only returns Double for f.p.) + lbits = Double.doubleToLongBits(n); + append('D'); + append((char)(lbits >> 48)); + append((char)(lbits >> 32)); + append((char)(lbits >> 16)); + append((char)lbits); + } + else { + // we can ignore negative values, bc they're already prefixed + // by NEG + if (lbits < 0) Kit.codeBug(); + + // will it fit in a char? + // this gives a short encoding for integer values up to 2^16. + if (lbits <= Character.MAX_VALUE) { + append('S'); + append((char)lbits); + } + else { // Integral, but won't fit in a char. Store as a long. + append('J'); + append((char)(lbits >> 48)); + append((char)(lbits >> 32)); + append((char)(lbits >> 16)); + append((char)lbits); + } + } + } + + private void appendString(String str) + { + int L = str.length(); + int lengthEncodingSize = 1; + if (L >= 0x8000) { + lengthEncodingSize = 2; + } + int nextTop = sourceTop + lengthEncodingSize + L; + if (nextTop > sourceBuffer.length) { + increaseSourceCapacity(nextTop); + } + if (L >= 0x8000) { + // Use 2 chars to encode strings exceeding 32K, were the highest + // bit in the first char indicates presence of the next byte + sourceBuffer[sourceTop] = (char)(0x8000 | (L >>> 16)); + ++sourceTop; + } + sourceBuffer[sourceTop] = (char)L; + ++sourceTop; + str.getChars(0, L, sourceBuffer, sourceTop); + sourceTop = nextTop; + } + + private void append(char c) + { + if (sourceTop == sourceBuffer.length) { + increaseSourceCapacity(sourceTop + 1); + } + sourceBuffer[sourceTop] = c; + ++sourceTop; + } + + private void increaseSourceCapacity(int minimalCapacity) + { + // Call this only when capacity increase is must + if (minimalCapacity <= sourceBuffer.length) Kit.codeBug(); + int newCapacity = sourceBuffer.length * 2; + if (newCapacity < minimalCapacity) { + newCapacity = minimalCapacity; + } + char[] tmp = new char[newCapacity]; + System.arraycopy(sourceBuffer, 0, tmp, 0, sourceTop); + sourceBuffer = tmp; + } + + private String sourceToString(int offset) + { + if (offset < 0 || sourceTop < offset) Kit.codeBug(); + return new String(sourceBuffer, offset, sourceTop - offset); + } + + /** + * Decompile the source information associated with this js + * function/script back into a string. For the most part, this + * just means translating tokens back to their string + * representations; there's a little bit of lookahead logic to + * decide the proper spacing/indentation. Most of the work in + * mapping the original source to the prettyprinted decompiled + * version is done by the parser. + * + * @param source encoded source tree presentation + * + * @param flags flags to select output format + * + * @param properties indentation properties + * + */ + public static String decompile(String source, int flags, + UintMap properties) + { + int length = source.length(); + if (length == 0) { return ""; } + + int indent = properties.getInt(INITIAL_INDENT_PROP, 0); + if (indent < 0) throw new IllegalArgumentException(); + int indentGap = properties.getInt(INDENT_GAP_PROP, 4); + if (indentGap < 0) throw new IllegalArgumentException(); + int caseGap = properties.getInt(CASE_GAP_PROP, 2); + if (caseGap < 0) throw new IllegalArgumentException(); + + StringBuffer result = new StringBuffer(); + boolean justFunctionBody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + boolean toSource = (0 != (flags & Decompiler.TO_SOURCE_FLAG)); + + // Spew tokens in source, for debugging. + // as TYPE number char + if (printSource) { + System.err.println("length:" + length); + for (int i = 0; i < length; ++i) { + // Note that tokenToName will fail unless Context.printTrees + // is true. + String tokenname = null; + if (Token.printNames) { + tokenname = Token.name(source.charAt(i)); + } + if (tokenname == null) { + tokenname = "---"; + } + String pad = tokenname.length() > 7 + ? "\t" + : "\t\t"; + System.err.println + (tokenname + + pad + (int)source.charAt(i) + + "\t'" + ScriptRuntime.escapeString + (source.substring(i, i+1)) + + "'"); + } + System.err.println(); + } + + int braceNesting = 0; + boolean afterFirstEOL = false; + int i = 0; + int topFunctionType; + if (source.charAt(i) == Token.SCRIPT) { + ++i; + topFunctionType = -1; + } else { + topFunctionType = source.charAt(i + 1); + } + + if (!toSource) { + // add an initial newline to exactly match js. + result.append('\n'); + for (int j = 0; j < indent; j++) + result.append(' '); + } else { + if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { + result.append('('); + } + } + + while (i < length) { + switch(source.charAt(i)) { + case Token.GET: + case Token.SET: + result.append(source.charAt(i) == Token.GET ? "get " : "set "); + ++i; + i = printSourceString(source, i + 1, false, result); + // Now increment one more to get past the FUNCTION token + ++i; + break; + + case Token.NAME: + case Token.REGEXP: // re-wrapped in '/'s in parser... + i = printSourceString(source, i + 1, false, result); + continue; + + case Token.STRING: + i = printSourceString(source, i + 1, true, result); + continue; + + case Token.NUMBER: + i = printSourceNumber(source, i + 1, result); + continue; + + case Token.TRUE: + result.append("true"); + break; + + case Token.FALSE: + result.append("false"); + break; + + case Token.NULL: + result.append("null"); + break; + + case Token.THIS: + result.append("this"); + break; + + case Token.FUNCTION: + ++i; // skip function type + result.append("function "); + break; + + case FUNCTION_END: + // Do nothing + break; + + case Token.COMMA: + result.append(", "); + break; + + case Token.LC: + ++braceNesting; + if (Token.EOL == getNext(source, length, i)) + indent += indentGap; + result.append('{'); + break; + + case Token.RC: { + --braceNesting; + /* don't print the closing RC if it closes the + * toplevel function and we're called from + * decompileFunctionBody. + */ + if (justFunctionBody && braceNesting == 0) + break; + + result.append('}'); + switch (getNext(source, length, i)) { + case Token.EOL: + case FUNCTION_END: + indent -= indentGap; + break; + case Token.WHILE: + case Token.ELSE: + indent -= indentGap; + result.append(' '); + break; + } + break; + } + case Token.LP: + result.append('('); + break; + + case Token.RP: + result.append(')'); + if (Token.LC == getNext(source, length, i)) + result.append(' '); + break; + + case Token.LB: + result.append('['); + break; + + case Token.RB: + result.append(']'); + break; + + case Token.EOL: { + if (toSource) break; + boolean newLine = true; + if (!afterFirstEOL) { + afterFirstEOL = true; + if (justFunctionBody) { + /* throw away just added 'function name(...) {' + * and restore the original indent + */ + result.setLength(0); + indent -= indentGap; + newLine = false; + } + } + if (newLine) { + result.append('\n'); + } + + /* add indent if any tokens remain, + * less setback if next token is + * a label, case or default. + */ + if (i + 1 < length) { + int less = 0; + int nextToken = source.charAt(i + 1); + if (nextToken == Token.CASE + || nextToken == Token.DEFAULT) + { + less = indentGap - caseGap; + } else if (nextToken == Token.RC) { + less = indentGap; + } + + /* elaborate check against label... skip past a + * following inlined NAME and look for a COLON. + */ + else if (nextToken == Token.NAME) { + int afterName = getSourceStringEnd(source, i + 2); + if (source.charAt(afterName) == Token.COLON) + less = indentGap; + } + + for (; less < indent; less++) + result.append(' '); + } + break; + } + case Token.DOT: + result.append('.'); + break; + + case Token.NEW: + result.append("new "); + break; + + case Token.DELPROP: + result.append("delete "); + break; + + case Token.IF: + result.append("if "); + break; + + case Token.ELSE: + result.append("else "); + break; + + case Token.FOR: + result.append("for "); + break; + + case Token.IN: + result.append(" in "); + break; + + case Token.WITH: + result.append("with "); + break; + + case Token.WHILE: + result.append("while "); + break; + + case Token.DO: + result.append("do "); + break; + + case Token.TRY: + result.append("try "); + break; + + case Token.CATCH: + result.append("catch "); + break; + + case Token.FINALLY: + result.append("finally "); + break; + + case Token.THROW: + result.append("throw "); + break; + + case Token.SWITCH: + result.append("switch "); + break; + + case Token.BREAK: + result.append("break"); + if (Token.NAME == getNext(source, length, i)) + result.append(' '); + break; + + case Token.CONTINUE: + result.append("continue"); + if (Token.NAME == getNext(source, length, i)) + result.append(' '); + break; + + case Token.CASE: + result.append("case "); + break; + + case Token.DEFAULT: + result.append("default"); + break; + + case Token.RETURN: + result.append("return"); + if (Token.SEMI != getNext(source, length, i)) + result.append(' '); + break; + + case Token.VAR: + result.append("var "); + break; + + case Token.LET: + result.append("let "); + break; + + case Token.SEMI: + result.append(';'); + if (Token.EOL != getNext(source, length, i)) { + // separators in FOR + result.append(' '); + } + break; + + case Token.ASSIGN: + result.append(" = "); + break; + + case Token.ASSIGN_ADD: + result.append(" += "); + break; + + case Token.ASSIGN_SUB: + result.append(" -= "); + break; + + case Token.ASSIGN_MUL: + result.append(" *= "); + break; + + case Token.ASSIGN_DIV: + result.append(" /= "); + break; + + case Token.ASSIGN_MOD: + result.append(" %= "); + break; + + case Token.ASSIGN_BITOR: + result.append(" |= "); + break; + + case Token.ASSIGN_BITXOR: + result.append(" ^= "); + break; + + case Token.ASSIGN_BITAND: + result.append(" &= "); + break; + + case Token.ASSIGN_LSH: + result.append(" <<= "); + break; + + case Token.ASSIGN_RSH: + result.append(" >>= "); + break; + + case Token.ASSIGN_URSH: + result.append(" >>>= "); + break; + + case Token.HOOK: + result.append(" ? "); + break; + + case Token.OBJECTLIT: + // pun OBJECTLIT to mean colon in objlit property + // initialization. + // This needs to be distinct from COLON in the general case + // to distinguish from the colon in a ternary... which needs + // different spacing. + result.append(':'); + break; + + case Token.COLON: + if (Token.EOL == getNext(source, length, i)) + // it's the end of a label + result.append(':'); + else + // it's the middle part of a ternary + result.append(" : "); + break; + + case Token.OR: + result.append(" || "); + break; + + case Token.AND: + result.append(" && "); + break; + + case Token.BITOR: + result.append(" | "); + break; + + case Token.BITXOR: + result.append(" ^ "); + break; + + case Token.BITAND: + result.append(" & "); + break; + + case Token.SHEQ: + result.append(" === "); + break; + + case Token.SHNE: + result.append(" !== "); + break; + + case Token.EQ: + result.append(" == "); + break; + + case Token.NE: + result.append(" != "); + break; + + case Token.LE: + result.append(" <= "); + break; + + case Token.LT: + result.append(" < "); + break; + + case Token.GE: + result.append(" >= "); + break; + + case Token.GT: + result.append(" > "); + break; + + case Token.INSTANCEOF: + result.append(" instanceof "); + break; + + case Token.LSH: + result.append(" << "); + break; + + case Token.RSH: + result.append(" >> "); + break; + + case Token.URSH: + result.append(" >>> "); + break; + + case Token.TYPEOF: + result.append("typeof "); + break; + + case Token.VOID: + result.append("void "); + break; + + case Token.CONST: + result.append("const "); + break; + + case Token.YIELD: + result.append("yield "); + break; + + case Token.NOT: + result.append('!'); + break; + + case Token.BITNOT: + result.append('~'); + break; + + case Token.POS: + result.append('+'); + break; + + case Token.NEG: + result.append('-'); + break; + + case Token.INC: + result.append("++"); + break; + + case Token.DEC: + result.append("--"); + break; + + case Token.ADD: + result.append(" + "); + break; + + case Token.SUB: + result.append(" - "); + break; + + case Token.MUL: + result.append(" * "); + break; + + case Token.DIV: + result.append(" / "); + break; + + case Token.MOD: + result.append(" % "); + break; + + case Token.COLONCOLON: + result.append("::"); + break; + + case Token.DOTDOT: + result.append(".."); + break; + + case Token.DOTQUERY: + result.append(".("); + break; + + case Token.XMLATTR: + result.append('@'); + break; + + default: + // If we don't know how to decompile it, raise an exception. + throw new RuntimeException("Token: " + + Token.name(source.charAt(i))); + } + ++i; + } + + if (!toSource) { + // add that trailing newline if it's an outermost function. + if (!justFunctionBody) + result.append('\n'); + } else { + if (topFunctionType == FunctionNode.FUNCTION_EXPRESSION) { + result.append(')'); + } + } + + return result.toString(); + } + + private static int getNext(String source, int length, int i) + { + return (i + 1 < length) ? source.charAt(i + 1) : Token.EOF; + } + + private static int getSourceStringEnd(String source, int offset) + { + return printSourceString(source, offset, false, null); + } + + private static int printSourceString(String source, int offset, + boolean asQuotedString, + StringBuffer sb) + { + int length = source.charAt(offset); + ++offset; + if ((0x8000 & length) != 0) { + length = ((0x7FFF & length) << 16) | source.charAt(offset); + ++offset; + } + if (sb != null) { + String str = source.substring(offset, offset + length); + if (!asQuotedString) { + sb.append(str); + } else { + sb.append('"'); + sb.append(ScriptRuntime.escapeString(str)); + sb.append('"'); + } + } + return offset + length; + } + + private static int printSourceNumber(String source, int offset, + StringBuffer sb) + { + double number = 0.0; + char type = source.charAt(offset); + ++offset; + if (type == 'S') { + if (sb != null) { + int ival = source.charAt(offset); + number = ival; + } + ++offset; + } else if (type == 'J' || type == 'D') { + if (sb != null) { + long lbits; + lbits = (long)source.charAt(offset) << 48; + lbits |= (long)source.charAt(offset + 1) << 32; + lbits |= (long)source.charAt(offset + 2) << 16; + lbits |= source.charAt(offset + 3); + if (type == 'J') { + number = lbits; + } else { + number = Double.longBitsToDouble(lbits); + } + } + offset += 4; + } else { + // Bad source + throw new RuntimeException(); + } + if (sb != null) { + sb.append(ScriptRuntime.numberToString(number, 10)); + } + return offset; + } + + private char[] sourceBuffer = new char[128]; + +// Per script/function source buffer top: parent source does not include a +// nested functions source and uses function index as a reference instead. + private int sourceTop; + +// whether to do a debug print of the source information, when decompiling. + private static final boolean printSource = false; + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefaultErrorReporter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefaultErrorReporter.java new file mode 100644 index 0000000..c7d93d4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefaultErrorReporter.java @@ -0,0 +1,113 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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; + +/** + * This is the default error reporter for JavaScript. + * + * @author Norris Boyd + */ +class DefaultErrorReporter implements ErrorReporter +{ + static final DefaultErrorReporter instance = new DefaultErrorReporter(); + + private boolean forEval; + private ErrorReporter chainedReporter; + + private DefaultErrorReporter() { } + + static ErrorReporter forEval(ErrorReporter reporter) + { + DefaultErrorReporter r = new DefaultErrorReporter(); + r.forEval = true; + r.chainedReporter = reporter; + return r; + } + + public void warning(String message, String sourceURI, int line, + String lineText, int lineOffset) + { + if (chainedReporter != null) { + chainedReporter.warning( + message, sourceURI, line, lineText, lineOffset); + } else { + // Do nothing + } + } + + public void error(String message, String sourceURI, int line, + String lineText, int lineOffset) + { + if (forEval) { + // Assume error message strings that start with "TypeError: " + // should become TypeError exceptions. A bit of a hack, but we + // don't want to change the ErrorReporter interface. + String error = "SyntaxError"; + final String TYPE_ERROR_NAME = "TypeError"; + final String DELIMETER = ": "; + final String prefix = TYPE_ERROR_NAME + DELIMETER; + if (message.startsWith(prefix)) { + error = TYPE_ERROR_NAME; + message = message.substring(prefix.length()); + } + throw ScriptRuntime.constructError(error, message, sourceURI, + line, lineText, lineOffset); + } + if (chainedReporter != null) { + chainedReporter.error( + message, sourceURI, line, lineText, lineOffset); + } else { + throw runtimeError( + message, sourceURI, line, lineText, lineOffset); + } + } + + public EvaluatorException runtimeError(String message, String sourceURI, + int line, String lineText, + int lineOffset) + { + if (chainedReporter != null) { + return chainedReporter.runtimeError( + message, sourceURI, line, lineText, lineOffset); + } else { + return new EvaluatorException( + message, sourceURI, line, lineText, lineOffset); + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefiningClassLoader.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefiningClassLoader.java new file mode 100644 index 0000000..5864b5d --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/DefiningClassLoader.java @@ -0,0 +1,88 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * Patrick Beard + * Igor Bukanov + * + * 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; + +/** + * Load generated classes. + * + * @author Norris Boyd + */ +public class DefiningClassLoader extends ClassLoader + implements GeneratedClassLoader +{ + public DefiningClassLoader() { + this.parentLoader = getClass().getClassLoader(); + } + + public DefiningClassLoader(ClassLoader parentLoader) { + this.parentLoader = parentLoader; + } + + public Class defineClass(String name, byte[] data) { + // Use our own protection domain for the generated classes. + // TODO: we might want to use a separate protection domain for classes + // compiled from scripts, based on where the script was loaded from. + return super.defineClass(name, data, 0, data.length, + SecurityUtilities.getProtectionDomain(getClass())); + } + + public void linkClass(Class cl) { + resolveClass(cl); + } + + public Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + Class cl = findLoadedClass(name); + if (cl == null) { + if (parentLoader != null) { + cl = parentLoader.loadClass(name); + } else { + cl = findSystemClass(name); + } + } + if (resolve) { + resolveClass(cl); + } + return cl; + } + + private final ClassLoader parentLoader; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Delegator.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Delegator.java new file mode 100644 index 0000000..e044863 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Delegator.java @@ -0,0 +1,266 @@ +/* -*- 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 Delegator.java, released + * Sep 27, 2000. + * + * The Initial Developer of the Original Code is + * Matthias Radestock. <matthias@sorted.org>. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This is a helper class for implementing wrappers around Scriptable + * objects. It implements the Function interface and delegates all + * invocations to a delegee Scriptable object. The normal use of this + * class involves creating a sub-class and overriding one or more of + * the methods. + * + * A useful application is the implementation of interceptors, + * pre/post conditions, debugging. + * + * @see Function + * @see Scriptable + * @author Matthias Radestock + */ + +public class Delegator implements Function { + + protected Scriptable obj = null; + + /** + * Create a Delegator prototype. + * + * This constructor should only be used for creating prototype + * objects of Delegator. + * + * @see org.mozilla.javascript.Delegator#construct + */ + public Delegator() { + } + + /** + * Create a new Delegator that forwards requests to a delegee + * Scriptable object. + * + * @param obj the delegee + * @see org.mozilla.javascript.Scriptable + */ + public Delegator(Scriptable obj) { + this.obj = obj; + } + + /** + * Crete new Delegator instance. + * The default implementation calls this.getClass().newInstance(). + * + * @see #construct(Context cx, Scriptable scope, Object[] args) + */ + protected Delegator newInstance() + { + try { + return this.getClass().newInstance(); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + + /** + * Retrieve the delegee. + * + * @return the delegee + */ + public Scriptable getDelegee() { + return obj; + } + /** + * Set the delegee. + * + * @param obj the delegee + * @see org.mozilla.javascript.Scriptable + */ + public void setDelegee(Scriptable obj) { + this.obj = obj; + } + /** + * @see org.mozilla.javascript.Scriptable#getClassName + */ + public String getClassName() { + return obj.getClassName(); + } + /** + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + */ + public Object get(String name, Scriptable start) { + return obj.get(name,start); + } + /** + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + */ + public Object get(int index, Scriptable start) { + return obj.get(index,start); + } + /** + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + */ + public boolean has(String name, Scriptable start) { + return obj.has(name,start); + } + /** + * @see org.mozilla.javascript.Scriptable#has(int, Scriptable) + */ + public boolean has(int index, Scriptable start) { + return obj.has(index,start); + } + /** + * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) + */ + public void put(String name, Scriptable start, Object value) { + obj.put(name,start,value); + } + /** + * @see org.mozilla.javascript.Scriptable#put(int, Scriptable, Object) + */ + public void put(int index, Scriptable start, Object value) { + obj.put(index,start,value); + } + /** + * @see org.mozilla.javascript.Scriptable#delete(String) + */ + public void delete(String name) { + obj.delete(name); + } + /** + * @see org.mozilla.javascript.Scriptable#delete(int) + */ + public void delete(int index) { + obj.delete(index); + } + /** + * @see org.mozilla.javascript.Scriptable#getPrototype + */ + public Scriptable getPrototype() { + return obj.getPrototype(); + } + /** + * @see org.mozilla.javascript.Scriptable#setPrototype + */ + public void setPrototype(Scriptable prototype) { + obj.setPrototype(prototype); + } + /** + * @see org.mozilla.javascript.Scriptable#getParentScope + */ + public Scriptable getParentScope() { + return obj.getParentScope(); + } + /** + * @see org.mozilla.javascript.Scriptable#setParentScope + */ + public void setParentScope(Scriptable parent) { + obj.setParentScope(parent); + } + /** + * @see org.mozilla.javascript.Scriptable#getIds + */ + public Object[] getIds() { + return obj.getIds(); + } + /** + * Note that this method does not get forwarded to the delegee if + * the <code>hint</code> parameter is null, + * <code>ScriptRuntime.ScriptableClass</code> or + * <code>ScriptRuntime.FunctionClass</code>. Instead the object + * itself is returned. + * + * @param hint the type hint + * @return the default value + * + * @see org.mozilla.javascript.Scriptable#getDefaultValue + */ + public Object getDefaultValue(Class hint) { + return (hint == null || + hint == ScriptRuntime.ScriptableClass || + hint == ScriptRuntime.FunctionClass) ? + this : obj.getDefaultValue(hint); + } + /** + * @see org.mozilla.javascript.Scriptable#hasInstance + */ + public boolean hasInstance(Scriptable instance) { + return obj.hasInstance(instance); + } + /** + * @see org.mozilla.javascript.Function#call + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return ((Function)obj).call(cx,scope,thisObj,args); + } + + /** + * Note that if the <code>delegee</code> is <code>null</code>, + * this method creates a new instance of the Delegator itself + * rathert than forwarding the call to the + * <code>delegee</code>. This permits the use of Delegator + * prototypes. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except + * when the function is called from a closure. + * @param args the array of arguments + * @return the allocated object + * + * @see Function#construct(Context, Scriptable, Object[]) + */ + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + if (obj == null) { + //this little trick allows us to declare prototype objects for + //Delegators + Delegator n = newInstance(); + Scriptable delegee; + if (args.length == 0) { + delegee = new NativeObject(); + } else { + delegee = ScriptRuntime.toObject(cx, scope, args[0]); + } + n.setDelegee(delegee); + return n; + } + else { + return ((Function)obj).construct(cx,scope,args); + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EcmaError.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EcmaError.java new file mode 100644 index 0000000..1fd8f03 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EcmaError.java @@ -0,0 +1,160 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * The class of exceptions raised by the engine as described in + * ECMA edition 3. See section 15.11.6 in particular. + */ +public class EcmaError extends RhinoException +{ + static final long serialVersionUID = -6261226256957286699L; + + private String errorName; + private String errorMessage; + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param sourceName the name of the source reponsible for the error + * @param lineNumber the line number of the source + * @param columnNumber the columnNumber of the source (may be zero if + * unknown) + * @param lineSource the source of the line containing the error (may be + * null if unknown) + */ + EcmaError(String errorName, String errorMessage, + String sourceName, int lineNumber, + String lineSource, int columnNumber) + { + recordErrorOrigin(sourceName, lineNumber, lineSource, columnNumber); + this.errorName = errorName; + this.errorMessage = errorMessage; + } + + /** + * @deprecated EcmaError error instances should not be constructed + * explicitly since they are generated by the engine. + */ + public EcmaError(Scriptable nativeError, String sourceName, + int lineNumber, int columnNumber, String lineSource) + { + this("InternalError", ScriptRuntime.toString(nativeError), + sourceName, lineNumber, lineSource, columnNumber); + } + + public String details() + { + return errorName+": "+errorMessage; + } + + /** + * Gets the name of the error. + * + * ECMA edition 3 defines the following + * errors: EvalError, RangeError, ReferenceError, + * SyntaxError, TypeError, and URIError. Additional error names + * may be added in the future. + * + * See ECMA edition 3, 15.11.7.9. + * + * @return the name of the error. + */ + public String getName() + { + return errorName; + } + + /** + * Gets the message corresponding to the error. + * + * See ECMA edition 3, 15.11.7.10. + * + * @return an implemenation-defined string describing the error. + */ + public String getErrorMessage() + { + return errorMessage; + } + + /** + * @deprecated Use {@link RhinoException#sourceName()} from the super class. + */ + public String getSourceName() + { + return sourceName(); + } + + /** + * @deprecated Use {@link RhinoException#lineNumber()} from the super class. + */ + public int getLineNumber() + { + return lineNumber(); + } + + /** + * @deprecated + * Use {@link RhinoException#columnNumber()} from the super class. + */ + public int getColumnNumber() { + return columnNumber(); + } + + /** + * @deprecated Use {@link RhinoException#lineSource()} from the super class. + */ + public String getLineSource() { + return lineSource(); + } + + /** + * @deprecated + * Always returns <b>null</b>. + */ + public Scriptable getErrorObject() + { + return null; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ErrorReporter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ErrorReporter.java new file mode 100644 index 0000000..4649370 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ErrorReporter.java @@ -0,0 +1,106 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface defines a protocol for the reporting of + * errors during JavaScript translation or execution. + * + * @author Norris Boyd + */ + +public interface ErrorReporter { + + /** + * Report a warning. + * + * The implementing class may choose to ignore the warning + * if it desires. + * + * @param message a String describing the warning + * @param sourceName a String describing the JavaScript source + * where the warning occured; typically a filename or URL + * @param line the line number associated with the warning + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void warning(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Report an error. + * + * The implementing class is free to throw an exception if + * it desires. + * + * If execution has not yet begun, the JavaScript engine is + * free to find additional errors rather than terminating + * the translation. It will not execute a script that had + * errors, however. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + */ + void error(String message, String sourceName, int line, + String lineSource, int lineOffset); + + /** + * Creates an EvaluatorException that may be thrown. + * + * runtimeErrors, unlike errors, will always terminate the + * current script. + * + * @param message a String describing the error + * @param sourceName a String describing the JavaScript source + * where the error occured; typically a filename or URL + * @param line the line number associated with the error + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return an EvaluatorException that will be thrown. + */ + EvaluatorException runtimeError(String message, String sourceName, + int line, String lineSource, + int lineOffset); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Evaluator.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Evaluator.java new file mode 100644 index 0000000..e222af3 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Evaluator.java @@ -0,0 +1,118 @@ +/* -*- 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. + * + * Contributor(s): + * Norris Boyd + * + * 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.util.List; + +/** + * Abstraction of evaluation, which can be implemented either by an + * interpreter or compiler. + */ +public interface Evaluator { + + /** + * Compile the script or function from intermediate representation + * tree into an executable form. + * + * @param compilerEnv Compiler environment + * @param tree intermediate representation + * @param encodedSource encoding of the source code for decompilation + * @param returnFunction if true, compiling a function + * @return an opaque object that can be passed to either + * createFunctionObject or createScriptObject, depending on the + * value of returnFunction + */ + public Object compile(CompilerEnvirons compilerEnv, + ScriptOrFnNode tree, + String encodedSource, + boolean returnFunction); + + /** + * Create a function object. + * + * @param cx Current context + * @param scope scope of the function + * @param bytecode opaque object returned by compile + * @param staticSecurityDomain security domain + * @return Function object that can be called + */ + public Function createFunctionObject(Context cx, Scriptable scope, + Object bytecode, Object staticSecurityDomain); + + /** + * Create a script object. + * + * @param bytecode opaque object returned by compile + * @param staticSecurityDomain security domain + * @return Script object that can be evaluated + */ + public Script createScriptObject(Object bytecode, + Object staticSecurityDomain); + + /** + * Capture stack information from the given exception. + * @param ex an exception thrown during execution + */ + public void captureStackInfo(RhinoException ex); + + /** + * Get the source position information by examining the stack. + * @param cx Context + * @param linep Array object of length >= 1; getSourcePositionFromStack + * will assign the line number to linep[0]. + * @return the name of the file or other source container + */ + public String getSourcePositionFromStack(Context cx, int[] linep); + + /** + * Given a native stack trace, patch it with script-specific source + * and line information + * @param ex exception + * @param nativeStackTrace the native stack trace + * @return patched stack trace + */ + public String getPatchedStack(RhinoException ex, + String nativeStackTrace); + + /** + * Get the script stack for the given exception + * @param ex exception from execution + * @return list of strings for the stack trace + */ + public List getScriptStack(RhinoException ex); + + /** + * Mark the given script to indicate it was created by a call to + * eval() or to a Function constructor. + * @param script script to mark as from eval + */ + public void setEvalScriptFlag(Script script); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EvaluatorException.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EvaluatorException.java new file mode 100644 index 0000000..7b4e7cc --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/EvaluatorException.java @@ -0,0 +1,123 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public class EvaluatorException extends RhinoException +{ + static final long serialVersionUID = -8743165779676009808L; + + public EvaluatorException(String detail) + { + super(detail); + } + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param detail the error message + * @param sourceName the name of the source reponsible for the error + * @param lineNumber the line number of the source + */ + public EvaluatorException(String detail, String sourceName, + int lineNumber) + { + this(detail, sourceName, lineNumber, null, 0); + } + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param detail the error message + * @param sourceName the name of the source responsible for the error + * @param lineNumber the line number of the source + * @param columnNumber the columnNumber of the source (may be zero if + * unknown) + * @param lineSource the source of the line containing the error (may be + * null if unknown) + */ + public EvaluatorException(String detail, String sourceName, int lineNumber, + String lineSource, int columnNumber) + { + super(detail); + recordErrorOrigin(sourceName, lineNumber, lineSource, columnNumber); + } + + /** + * @deprecated Use {@link RhinoException#sourceName()} from the super class. + */ + public String getSourceName() + { + return sourceName(); + } + + /** + * @deprecated Use {@link RhinoException#lineNumber()} from the super class. + */ + public int getLineNumber() + { + return lineNumber(); + } + + /** + * @deprecated Use {@link RhinoException#columnNumber()} from the super class. + */ + public int getColumnNumber() + { + return columnNumber(); + } + + /** + * @deprecated Use {@link RhinoException#lineSource()} from the super class. + */ + public String getLineSource() + { + return lineSource(); + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Function.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Function.java new file mode 100644 index 0000000..a4377e6 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Function.java @@ -0,0 +1,84 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all functions in JavaScript must implement. + * The interface provides for calling functions and constructors. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public interface Function extends Scriptable, Callable +{ + /** + * Call the function. + * + * Note that the array of arguments is not guaranteed to have + * length greater than 0. + * + * @param cx the current Context for this thread + * @param scope the scope to execute the function relative to. This is + * set to the value returned by getParentScope() except + * when the function is called from a closure. + * @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); + + /** + * Call the function as a constructor. + * + * This method is invoked by the runtime in order to satisfy a use + * of the JavaScript <code>new</code> operator. This method is + * expected to create a new object and return it. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except + * when the function is called from a closure. + * @param args the array of arguments + * @return the allocated object + */ + public Scriptable construct(Context cx, Scriptable scope, Object[] args); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionNode.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionNode.java new file mode 100644 index 0000000..484167e --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionNode.java @@ -0,0 +1,117 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * 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.util.ArrayList; +import java.util.HashMap; + +public class FunctionNode extends ScriptOrFnNode { + + public FunctionNode(String name) { + super(Token.FUNCTION); + functionName = name; + } + + public String getFunctionName() { + return functionName; + } + + public boolean requiresActivation() { + return itsNeedsActivation; + } + + public boolean getIgnoreDynamicScope() { + return itsIgnoreDynamicScope; + } + + public boolean isGenerator() { + return itsIsGenerator; + } + + public void addResumptionPoint(Node target) { + if (generatorResumePoints == null) + generatorResumePoints = new ArrayList(); + generatorResumePoints.add(target); + } + + public ArrayList getResumptionPoints() { + return generatorResumePoints; + } + + public HashMap getLiveLocals() { + return liveLocals; + } + + public void addLiveLocals(Node node, int[] locals) { + if (liveLocals == null) + liveLocals = new HashMap(); + liveLocals.put(node, locals); + } + + /** + * There are three types of functions that can be defined. The first + * is a function statement. This is a function appearing as a top-level + * statement (i.e., not nested inside some other statement) in either a + * script or a function. + * + * The second is a function expression, which is a function appearing in + * an expression except for the third type, which is... + * + * The third type is a function expression where the expression is the + * top-level expression in an expression statement. + * + * The three types of functions have different treatment and must be + * distinguished. + */ + public static final int FUNCTION_STATEMENT = 1; + public static final int FUNCTION_EXPRESSION = 2; + public static final int FUNCTION_EXPRESSION_STATEMENT = 3; + + public int getFunctionType() { + return itsFunctionType; + } + + String functionName; + int itsFunctionType; + boolean itsNeedsActivation; + boolean itsIgnoreDynamicScope; + boolean itsIsGenerator; + ArrayList generatorResumePoints; + HashMap liveLocals; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java new file mode 100644 index 0000000..8fa4e68 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/FunctionObject.java @@ -0,0 +1,569 @@ +/* -*- 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): + * Norris Boyd + * Igor Bukanov + * David C. Navas + * Ted Neward + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.io.*; + +public class FunctionObject extends BaseFunction +{ + static final long serialVersionUID = -5332312783643935019L; + + /** + * Create a JavaScript function object from a Java method. + * + * <p>The <code>member</code> argument must be either a java.lang.reflect.Method + * or a java.lang.reflect.Constructor and must match one of two forms.<p> + * + * The first form is a member with zero or more parameters + * of the following types: Object, String, boolean, Scriptable, + * int, or double. The Long type is not supported + * because the double representation of a long (which is the + * EMCA-mandated storage type for Numbers) may lose precision. + * If the member is a Method, the return value must be void or one + * of the types allowed for parameters.<p> + * + * The runtime will perform appropriate conversions based + * upon the type of the parameter. A parameter type of + * Object specifies that no conversions are to be done. A parameter + * of type String will use Context.toString to convert arguments. + * Similarly, parameters of type double, boolean, and Scriptable + * will cause Context.toNumber, Context.toBoolean, and + * Context.toObject, respectively, to be called.<p> + * + * If the method is not static, the Java 'this' value will + * correspond to the JavaScript 'this' value. Any attempt + * to call the function with a 'this' value that is not + * of the right Java type will result in an error.<p> + * + * The second form is the variable arguments (or "varargs") + * form. If the FunctionObject will be used as a constructor, + * the member must have the following parameters + * <pre> + * (Context cx, Object[] args, Function ctorObj, + * boolean inNewExpr)</pre> + * and if it is a Method, be static and return an Object result.<p> + * + * Otherwise, if the FunctionObject will <i>not</i> be used to define a + * constructor, the member must be a static Method with parameters + * (Context cx, Scriptable thisObj, Object[] args, + * Function funObj) </pre> + * <pre> + * and an Object result.<p> + * + * When the function varargs form is called as part of a function call, + * the <code>args</code> parameter contains the + * arguments, with <code>thisObj</code> + * set to the JavaScript 'this' value. <code>funObj</code> + * is the function object for the invoked function.<p> + * + * When the constructor varargs form is called or invoked while evaluating + * a <code>new</code> expression, <code>args</code> contains the + * arguments, <code>ctorObj</code> refers to this FunctionObject, and + * <code>inNewExpr</code> is true if and only if a <code>new</code> + * expression caused the call. This supports defining a function that + * has different behavior when called as a constructor than when + * invoked as a normal function call. (For example, the Boolean + * constructor, when called as a function, + * will convert to boolean rather than creating a new object.)<p> + * + * @param name the name of the function + * @param methodOrConstructor a java.lang.reflect.Method or a java.lang.reflect.Constructor + * that defines the object + * @param scope enclosing scope of function + * @see org.mozilla.javascript.Scriptable + */ + public FunctionObject(String name, Member methodOrConstructor, + Scriptable scope) + { + if (methodOrConstructor instanceof Constructor) { + member = new MemberBox((Constructor) methodOrConstructor); + isStatic = true; // well, doesn't take a 'this' + } else { + member = new MemberBox((Method) methodOrConstructor); + isStatic = member.isStatic(); + } + String methodName = member.getName(); + this.functionName = name; + Class[] types = member.argTypes; + int arity = types.length; + if (arity == 4 && (types[1].isArray() || types[2].isArray())) { + // Either variable args or an error. + if (types[1].isArray()) { + if (!isStatic || + types[0] != ScriptRuntime.ContextClass || + types[1].getComponentType() != ScriptRuntime.ObjectClass || + types[2] != ScriptRuntime.FunctionClass || + types[3] != Boolean.TYPE) + { + throw Context.reportRuntimeError1( + "msg.varargs.ctor", methodName); + } + parmsLength = VARARGS_CTOR; + } else { + if (!isStatic || + types[0] != ScriptRuntime.ContextClass || + types[1] != ScriptRuntime.ScriptableClass || + types[2].getComponentType() != ScriptRuntime.ObjectClass || + types[3] != ScriptRuntime.FunctionClass) + { + throw Context.reportRuntimeError1( + "msg.varargs.fun", methodName); + } + parmsLength = VARARGS_METHOD; + } + } else { + parmsLength = arity; + if (arity > 0) { + typeTags = new byte[arity]; + for (int i = 0; i != arity; ++i) { + int tag = getTypeTag(types[i]); + if (tag == JAVA_UNSUPPORTED_TYPE) { + throw Context.reportRuntimeError2( + "msg.bad.parms", types[i].getName(), methodName); + } + typeTags[i] = (byte)tag; + } + } + } + + if (member.isMethod()) { + Method method = member.method(); + Class returnType = method.getReturnType(); + if (returnType == Void.TYPE) { + hasVoidReturn = true; + } else { + returnTypeTag = getTypeTag(returnType); + } + } else { + Class ctorType = member.getDeclaringClass(); + if (!ScriptRuntime.ScriptableClass.isAssignableFrom(ctorType)) { + throw Context.reportRuntimeError1( + "msg.bad.ctor.return", ctorType.getName()); + } + } + + ScriptRuntime.setFunctionProtoAndParent(this, scope); + } + + /** + * @return One of <tt>JAVA_*_TYPE</tt> constants to indicate desired type + * or {@link #JAVA_UNSUPPORTED_TYPE} if the convertion is not + * possible + */ + public static int getTypeTag(Class type) + { + if (type == ScriptRuntime.StringClass) + return JAVA_STRING_TYPE; + if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) + return JAVA_INT_TYPE; + if (type == ScriptRuntime.BooleanClass || type == Boolean.TYPE) + return JAVA_BOOLEAN_TYPE; + if (type == ScriptRuntime.DoubleClass || type == Double.TYPE) + return JAVA_DOUBLE_TYPE; + if (ScriptRuntime.ScriptableClass.isAssignableFrom(type)) + return JAVA_SCRIPTABLE_TYPE; + if (type == ScriptRuntime.ObjectClass) + return JAVA_OBJECT_TYPE; + + // Note that the long type is not supported; see the javadoc for + // the constructor for this class + + return JAVA_UNSUPPORTED_TYPE; + } + + public static Object convertArg(Context cx, Scriptable scope, + Object arg, int typeTag) + { + switch (typeTag) { + case JAVA_STRING_TYPE: + if (arg instanceof String) + return arg; + return ScriptRuntime.toString(arg); + case JAVA_INT_TYPE: + if (arg instanceof Integer) + return arg; + return new Integer(ScriptRuntime.toInt32(arg)); + case JAVA_BOOLEAN_TYPE: + if (arg instanceof Boolean) + return arg; + return ScriptRuntime.toBoolean(arg) ? Boolean.TRUE + : Boolean.FALSE; + case JAVA_DOUBLE_TYPE: + if (arg instanceof Double) + return arg; + return new Double(ScriptRuntime.toNumber(arg)); + case JAVA_SCRIPTABLE_TYPE: + if (arg instanceof Scriptable) + return arg; + return ScriptRuntime.toObject(cx, scope, arg); + case JAVA_OBJECT_TYPE: + return arg; + default: + throw new IllegalArgumentException(); + } + } + + /** + * Return the value defined by the method used to construct the object + * (number of parameters of the method, or 1 if the method is a "varargs" + * form). + */ + public int getArity() { + return parmsLength < 0 ? 1 : parmsLength; + } + + /** + * Return the same value as {@link #getArity()}. + */ + public int getLength() { + return getArity(); + } + + public String getFunctionName() + { + return (functionName == null) ? "" : functionName; + } + + /** + * Get Java method or constructor this function represent. + */ + public Member getMethodOrConstructor() + { + if (member.isMethod()) { + return member.method(); + } else { + return member.ctor(); + } + } + + static Method findSingleMethod(Method[] methods, String name) + { + Method found = null; + for (int i = 0, N = methods.length; i != N; ++i) { + Method method = methods[i]; + if (method != null && name.equals(method.getName())) { + if (found != null) { + throw Context.reportRuntimeError2( + "msg.no.overload", name, + method.getDeclaringClass().getName()); + } + found = method; + } + } + return found; + } + + /** + * Returns all public methods declared by the specified class. This excludes + * inherited methods. + * + * @param clazz the class from which to pull public declared methods + * @return the public methods declared in the specified class + * @see Class#getDeclaredMethods() + */ + static Method[] getMethodList(Class clazz) { + Method[] methods = null; + try { + // getDeclaredMethods may be rejected by the security manager + // but getMethods is more expensive + if (!sawSecurityException) + methods = clazz.getDeclaredMethods(); + } catch (SecurityException e) { + // If we get an exception once, give up on getDeclaredMethods + sawSecurityException = true; + } + if (methods == null) { + methods = clazz.getMethods(); + } + int count = 0; + for (int i=0; i < methods.length; i++) { + if (sawSecurityException + ? methods[i].getDeclaringClass() != clazz + : !Modifier.isPublic(methods[i].getModifiers())) + { + methods[i] = null; + } else { + count++; + } + } + Method[] result = new Method[count]; + int j=0; + for (int i=0; i < methods.length; i++) { + if (methods[i] != null) + result[j++] = methods[i]; + } + return result; + } + + /** + * Define this function as a JavaScript constructor. + * <p> + * Sets up the "prototype" and "constructor" properties. Also + * calls setParent and setPrototype with appropriate values. + * Then adds the function object as a property of the given scope, using + * <code>prototype.getClassName()</code> + * as the name of the property. + * + * @param scope the scope in which to define the constructor (typically + * the global object) + * @param prototype the prototype object + * @see org.mozilla.javascript.Scriptable#setParentScope + * @see org.mozilla.javascript.Scriptable#setPrototype + * @see org.mozilla.javascript.Scriptable#getClassName + */ + public void addAsConstructor(Scriptable scope, Scriptable prototype) + { + initAsConstructor(scope, prototype); + defineProperty(scope, prototype.getClassName(), + this, ScriptableObject.DONTENUM); + } + + void initAsConstructor(Scriptable scope, Scriptable prototype) + { + ScriptRuntime.setFunctionProtoAndParent(this, scope); + setImmunePrototypeProperty(prototype); + + prototype.setParentScope(this); + + defineProperty(prototype, "constructor", this, + ScriptableObject.DONTENUM | + ScriptableObject.PERMANENT | + ScriptableObject.READONLY); + setParentScope(scope); + } + + /** + * @deprecated Use {@link #getTypeTag(Class)} + * and {@link #convertArg(Context, Scriptable, Object, int)} + * for type convertion. + */ + public static Object convertArg(Context cx, Scriptable scope, + Object arg, Class desired) + { + int tag = getTypeTag(desired); + if (tag == JAVA_UNSUPPORTED_TYPE) { + throw Context.reportRuntimeError1 + ("msg.cant.convert", desired.getName()); + } + return convertArg(cx, scope, arg, tag); + } + + /** + * Performs conversions on argument types if needed and + * invokes the underlying Java method or constructor. + * <p> + * Implements Function.call. + * + * @see org.mozilla.javascript.Function#call( + * Context, Scriptable, Scriptable, Object[]) + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + Object result; + boolean checkMethodResult = false; + + if (parmsLength < 0) { + if (parmsLength == VARARGS_METHOD) { + Object[] invokeArgs = { cx, thisObj, args, this }; + result = member.invoke(null, invokeArgs); + checkMethodResult = true; + } else { + boolean inNewExpr = (thisObj == null); + Boolean b = inNewExpr ? Boolean.TRUE : Boolean.FALSE; + Object[] invokeArgs = { cx, args, this, b }; + result = (member.isCtor()) + ? member.newInstance(invokeArgs) + : member.invoke(null, invokeArgs); + } + + } else { + if (!isStatic) { + Class clazz = member.getDeclaringClass(); + if (!clazz.isInstance(thisObj)) { + boolean compatible = false; + if (thisObj == scope) { + Scriptable parentScope = getParentScope(); + if (scope != parentScope) { + // Call with dynamic scope for standalone function, + // use parentScope as thisObj + compatible = clazz.isInstance(parentScope); + if (compatible) { + thisObj = parentScope; + } + } + } + if (!compatible) { + // Couldn't find an object to call this on. + throw ScriptRuntime.typeError1("msg.incompat.call", + functionName); + } + } + } + + Object[] invokeArgs; + if (parmsLength == args.length) { + // Do not allocate new argument array if java arguments are + // the same as the original js ones. + invokeArgs = args; + for (int i = 0; i != parmsLength; ++i) { + Object arg = args[i]; + Object converted = convertArg(cx, scope, arg, typeTags[i]); + if (arg != converted) { + if (invokeArgs == args) { + invokeArgs = args.clone(); + } + invokeArgs[i] = converted; + } + } + } else if (parmsLength == 0) { + invokeArgs = ScriptRuntime.emptyArgs; + } else { + invokeArgs = new Object[parmsLength]; + for (int i = 0; i != parmsLength; ++i) { + Object arg = (i < args.length) + ? args[i] + : Undefined.instance; + invokeArgs[i] = convertArg(cx, scope, arg, typeTags[i]); + } + } + + if (member.isMethod()) { + result = member.invoke(thisObj, invokeArgs); + checkMethodResult = true; + } else { + result = member.newInstance(invokeArgs); + } + + } + + if (checkMethodResult) { + if (hasVoidReturn) { + result = Undefined.instance; + } else if (returnTypeTag == JAVA_UNSUPPORTED_TYPE) { + result = cx.getWrapFactory().wrap(cx, scope, result, null); + } + // XXX: the code assumes that if returnTypeTag == JAVA_OBJECT_TYPE + // then the Java method did a proper job of converting the + // result to JS primitive or Scriptable to avoid + // potentially costly Context.javaToJS call. + } + + return result; + } + + /** + * Return new {@link Scriptable} instance using the default + * constructor for the class of the underlying Java method. + * Return null to indicate that the call method should be used to create + * new objects. + */ + public Scriptable createObject(Context cx, Scriptable scope) { + if (member.isCtor() || parmsLength == VARARGS_CTOR) { + return null; + } + Scriptable result; + try { + result = (Scriptable) member.getDeclaringClass().newInstance(); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + + result.setPrototype(getClassPrototype()); + result.setParentScope(getParentScope()); + return result; + } + + boolean isVarArgsMethod() { + return parmsLength == VARARGS_METHOD; + } + + boolean isVarArgsConstructor() { + return parmsLength == VARARGS_CTOR; + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + if (parmsLength > 0) { + Class[] types = member.argTypes; + typeTags = new byte[parmsLength]; + for (int i = 0; i != parmsLength; ++i) { + typeTags[i] = (byte)getTypeTag(types[i]); + } + } + if (member.isMethod()) { + Method method = member.method(); + Class returnType = method.getReturnType(); + if (returnType == Void.TYPE) { + hasVoidReturn = true; + } else { + returnTypeTag = getTypeTag(returnType); + } + } + } + + private static final short VARARGS_METHOD = -1; + private static final short VARARGS_CTOR = -2; + + private static boolean sawSecurityException; + + public static final int JAVA_UNSUPPORTED_TYPE = 0; + public static final int JAVA_STRING_TYPE = 1; + public static final int JAVA_INT_TYPE = 2; + public static final int JAVA_BOOLEAN_TYPE = 3; + public static final int JAVA_DOUBLE_TYPE = 4; + public static final int JAVA_SCRIPTABLE_TYPE = 5; + public static final int JAVA_OBJECT_TYPE = 6; + + MemberBox member; + private String functionName; + private transient byte[] typeTags; + private int parmsLength; + private transient boolean hasVoidReturn; + private transient int returnTypeTag; + private boolean isStatic; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/GeneratedClassLoader.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/GeneratedClassLoader.java new file mode 100644 index 0000000..0f73615 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/GeneratedClassLoader.java @@ -0,0 +1,66 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Interface to define classes from generated byte code. + */ +public interface GeneratedClassLoader { + + /** + * Define a new Java class. + * Classes created via this method should have the same class loader. + * + * @param name fully qualified class name + * @param data class byte code + * @return new class object + */ + public Class defineClass(String name, byte[] data); + + /** + * Link the given class. + * + * @param cl Class instance returned from the previous call to + * {@link #defineClass(String, byte[])} + * @see java.lang.ClassLoader + */ + public void linkClass(Class cl); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IRFactory.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IRFactory.java new file mode 100644 index 0000000..1f51cb1 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IRFactory.java @@ -0,0 +1,1607 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Ethan Hugg + * Bob Jervis + * Terry Lucas + * Milen Nankov + * + * 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.util.List; +import java.util.ArrayList; + +/** + * This class allows the creation of nodes, and follows the Factory pattern. + * + * @see Node + * @author Mike McCabe + * @author Norris Boyd + */ +final class IRFactory +{ + IRFactory(Parser parser) + { + this.parser = parser; + } + + ScriptOrFnNode createScript() + { + return new ScriptOrFnNode(Token.SCRIPT); + } + + /** + * Script (for associating file/url names with toplevel scripts.) + */ + void initScript(ScriptOrFnNode scriptNode, Node body) + { + Node children = body.getFirstChild(); + if (children != null) { scriptNode.addChildrenToBack(children); } + } + + /** + * Leaf + */ + Node createLeaf(int nodeType) + { + return new Node(nodeType); + } + + /** + * Statement leaf nodes. + */ + + Node createSwitch(Node expr, int lineno) + { + // + // The switch will be rewritten from: + // + // switch (expr) { + // case test1: statements1; + // ... + // default: statementsDefault; + // ... + // case testN: statementsN; + // } + // + // to: + // + // { + // switch (expr) { + // case test1: goto label1; + // ... + // case testN: goto labelN; + // } + // goto labelDefault; + // label1: + // statements1; + // ... + // labelDefault: + // statementsDefault; + // ... + // labelN: + // statementsN; + // breakLabel: + // } + // + // where inside switch each "break;" without label will be replaced + // by "goto breakLabel". + // + // If the original switch does not have the default label, then + // the transformed code would contain after the switch instead of + // goto labelDefault; + // the following goto: + // goto breakLabel; + // + + Node.Jump switchNode = new Node.Jump(Token.SWITCH, expr, lineno); + Node block = new Node(Token.BLOCK, switchNode); + return block; + } + + /** + * If caseExpression argument is null it indicate default label. + */ + void addSwitchCase(Node switchBlock, Node caseExpression, Node statements) + { + if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug(); + Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild(); + if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug(); + + Node gotoTarget = Node.newTarget(); + if (caseExpression != null) { + Node.Jump caseNode = new Node.Jump(Token.CASE, caseExpression); + caseNode.target = gotoTarget; + switchNode.addChildToBack(caseNode); + } else { + switchNode.setDefault(gotoTarget); + } + switchBlock.addChildToBack(gotoTarget); + switchBlock.addChildToBack(statements); + } + + void closeSwitch(Node switchBlock) + { + if (switchBlock.getType() != Token.BLOCK) throw Kit.codeBug(); + Node.Jump switchNode = (Node.Jump)switchBlock.getFirstChild(); + if (switchNode.getType() != Token.SWITCH) throw Kit.codeBug(); + + Node switchBreakTarget = Node.newTarget(); + // switchNode.target is only used by NodeTransformer + // to detect switch end + switchNode.target = switchBreakTarget; + + Node defaultTarget = switchNode.getDefault(); + if (defaultTarget == null) { + defaultTarget = switchBreakTarget; + } + + switchBlock.addChildAfter(makeJump(Token.GOTO, defaultTarget), + switchNode); + switchBlock.addChildToBack(switchBreakTarget); + } + + Node createVariables(int token, int lineno) + { + return new Node(token, lineno); + } + + Node createExprStatement(Node expr, int lineno) + { + int type; + if (parser.insideFunction()) { + type = Token.EXPR_VOID; + } else { + type = Token.EXPR_RESULT; + } + return new Node(type, expr, lineno); + } + + Node createExprStatementNoReturn(Node expr, int lineno) + { + return new Node(Token.EXPR_VOID, expr, lineno); + } + + Node createDefaultNamespace(Node expr, int lineno) + { + // default xml namespace requires activation + setRequiresActivation(); + Node n = createUnary(Token.DEFAULTNAMESPACE, expr); + Node result = createExprStatement(n, lineno); + return result; + } + + /** + * Name + */ + Node createName(String name) + { + checkActivationName(name, Token.NAME); + return Node.newString(Token.NAME, name); + } + + private Node createName(int type, String name, Node child) + { + Node result = createName(name); + result.setType(type); + if (child != null) + result.addChildToBack(child); + return result; + } + + /** + * String (for literals) + */ + Node createString(String string) + { + return Node.newString(string); + } + + /** + * Number (for literals) + */ + Node createNumber(double number) + { + return Node.newNumber(number); + } + + /** + * Catch clause of try/catch/finally + * @param varName the name of the variable to bind to the exception + * @param catchCond the condition under which to catch the exception. + * May be null if no condition is given. + * @param stmts the statements in the catch clause + * @param lineno the starting line number of the catch clause + */ + Node createCatch(String varName, Node catchCond, Node stmts, int lineno) + { + if (catchCond == null) { + catchCond = new Node(Token.EMPTY); + } + return new Node(Token.CATCH, createName(varName), + catchCond, stmts, lineno); + } + + /** + * Throw + */ + Node createThrow(Node expr, int lineno) + { + return new Node(Token.THROW, expr, lineno); + } + + /** + * Return + */ + Node createReturn(Node expr, int lineno) + { + return expr == null + ? new Node(Token.RETURN, lineno) + : new Node(Token.RETURN, expr, lineno); + } + + /** + * Debugger + */ + Node createDebugger(int lineno) + { + return new Node(Token.DEBUGGER, lineno); + } + + /** + * Label + */ + Node createLabel(int lineno) + { + return new Node.Jump(Token.LABEL, lineno); + } + + Node getLabelLoop(Node label) + { + return ((Node.Jump)label).getLoop(); + } + + /** + * Label + */ + Node createLabeledStatement(Node labelArg, Node statement) + { + Node.Jump label = (Node.Jump)labelArg; + + // Make a target and put it _after_ the statement + // node. And in the LABEL node, so breaks get the + // right target. + + Node breakTarget = Node.newTarget(); + Node block = new Node(Token.BLOCK, label, statement, breakTarget); + label.target = breakTarget; + + return block; + } + + /** + * Break (possibly labeled) + */ + Node createBreak(Node breakStatement, int lineno) + { + Node.Jump n = new Node.Jump(Token.BREAK, lineno); + Node.Jump jumpStatement; + int t = breakStatement.getType(); + if (t == Token.LOOP || t == Token.LABEL) { + jumpStatement = (Node.Jump)breakStatement; + } else if (t == Token.BLOCK + && breakStatement.getFirstChild().getType() == Token.SWITCH) + { + jumpStatement = (Node.Jump)breakStatement.getFirstChild(); + } else { + throw Kit.codeBug(); + } + n.setJumpStatement(jumpStatement); + return n; + } + + /** + * Continue (possibly labeled) + */ + Node createContinue(Node loop, int lineno) + { + if (loop.getType() != Token.LOOP) Kit.codeBug(); + Node.Jump n = new Node.Jump(Token.CONTINUE, lineno); + n.setJumpStatement((Node.Jump)loop); + return n; + } + + /** + * Statement block + * Creates the empty statement block + * Must make subsequent calls to add statements to the node + */ + Node createBlock(int lineno) + { + return new Node(Token.BLOCK, lineno); + } + + FunctionNode createFunction(String name) + { + return new FunctionNode(name); + } + + Node initFunction(FunctionNode fnNode, int functionIndex, + Node statements, int functionType) + { + fnNode.itsFunctionType = functionType; + fnNode.addChildToBack(statements); + + int functionCount = fnNode.getFunctionCount(); + if (functionCount != 0) { + // Functions containing other functions require activation objects + fnNode.itsNeedsActivation = true; + } + + if (functionType == FunctionNode.FUNCTION_EXPRESSION) { + String name = fnNode.getFunctionName(); + if (name != null && name.length() != 0) { + // A function expression needs to have its name as a + // variable (if it isn't already allocated as a variable). + // See ECMA Ch. 13. We add code to the beginning of the + // function to initialize a local variable of the + // function's name to the function value. + Node setFn = new Node(Token.EXPR_VOID, + new Node(Token.SETNAME, + Node.newString(Token.BINDNAME, name), + new Node(Token.THISFN))); + statements.addChildrenToFront(setFn); + } + } + + // Add return to end if needed. + Node lastStmt = statements.getLastChild(); + if (lastStmt == null || lastStmt.getType() != Token.RETURN) { + statements.addChildToBack(new Node(Token.RETURN)); + } + + Node result = Node.newString(Token.FUNCTION, + fnNode.getFunctionName()); + result.putIntProp(Node.FUNCTION_PROP, functionIndex); + return result; + } + + /** + * Add a child to the back of the given node. This function + * breaks the Factory abstraction, but it removes a requirement + * from implementors of Node. + */ + void addChildToBack(Node parent, Node child) + { + parent.addChildToBack(child); + } + + /** + * Create a node that can be used to hold lexically scoped variable + * definitions (via let declarations). + * + * @param token the token of the node to create + * @param lineno line number of source + * @return the created node + */ + Node createScopeNode(int token, int lineno) { + return new Node.Scope(token, lineno); + } + + /** + * Create loop node. The parser will later call + * createWhile|createDoWhile|createFor|createForIn + * to finish loop generation. + */ + Node createLoopNode(Node loopLabel, int lineno) + { + Node.Jump result = new Node.Scope(Token.LOOP, lineno); + if (loopLabel != null) { + ((Node.Jump)loopLabel).setLoop(result); + } + return result; + } + + /** + * While + */ + Node createWhile(Node loop, Node cond, Node body) + { + return createLoop((Node.Jump)loop, LOOP_WHILE, body, cond, + null, null); + } + + /** + * DoWhile + */ + Node createDoWhile(Node loop, Node body, Node cond) + { + return createLoop((Node.Jump)loop, LOOP_DO_WHILE, body, cond, + null, null); + } + + /** + * For + */ + Node createFor(Node loop, Node init, Node test, Node incr, Node body) + { + if (init.getType() == Token.LET) { + // rewrite "for (let i=s; i < N; i++)..." as + // "let (i=s) { for (; i < N; i++)..." so that "s" is evaluated + // outside the scope of the for. + Node.Scope let = Node.Scope.splitScope((Node.Scope)loop); + let.setType(Token.LET); + let.addChildrenToBack(init); + let.addChildToBack(createLoop((Node.Jump)loop, LOOP_FOR, body, test, + new Node(Token.EMPTY), incr)); + return let; + } + return createLoop((Node.Jump)loop, LOOP_FOR, body, test, init, incr); + } + + private Node createLoop(Node.Jump loop, int loopType, Node body, Node cond, + Node init, Node incr) + { + Node bodyTarget = Node.newTarget(); + Node condTarget = Node.newTarget(); + if (loopType == LOOP_FOR && cond.getType() == Token.EMPTY) { + cond = new Node(Token.TRUE); + } + Node.Jump IFEQ = new Node.Jump(Token.IFEQ, cond); + IFEQ.target = bodyTarget; + Node breakTarget = Node.newTarget(); + + loop.addChildToBack(bodyTarget); + loop.addChildrenToBack(body); + if (loopType == LOOP_WHILE || loopType == LOOP_FOR) { + // propagate lineno to condition + loop.addChildrenToBack(new Node(Token.EMPTY, loop.getLineno())); + } + loop.addChildToBack(condTarget); + loop.addChildToBack(IFEQ); + loop.addChildToBack(breakTarget); + + loop.target = breakTarget; + Node continueTarget = condTarget; + + if (loopType == LOOP_WHILE || loopType == LOOP_FOR) { + // Just add a GOTO to the condition in the do..while + loop.addChildToFront(makeJump(Token.GOTO, condTarget)); + + if (loopType == LOOP_FOR) { + int initType = init.getType(); + if (initType != Token.EMPTY) { + if (initType != Token.VAR && initType != Token.LET) { + init = new Node(Token.EXPR_VOID, init); + } + loop.addChildToFront(init); + } + Node incrTarget = Node.newTarget(); + loop.addChildAfter(incrTarget, body); + if (incr.getType() != Token.EMPTY) { + incr = new Node(Token.EXPR_VOID, incr); + loop.addChildAfter(incr, incrTarget); + } + continueTarget = incrTarget; + } + } + + loop.setContinue(continueTarget); + + return loop; + } + + /** + * For .. In + * + */ + Node createForIn(int declType, Node loop, Node lhs, Node obj, Node body, + boolean isForEach) + { + int destructuring = -1; + int destructuringLen = 0; + Node lvalue; + int type = lhs.getType(); + if (type == Token.VAR || type == Token.LET) { + Node lastChild = lhs.getLastChild(); + if (lhs.getFirstChild() != lastChild) { + /* + * check that there was only one variable given. + * we can't do this in the parser, because then the + * parser would have to know something about the + * 'init' node of the for-in loop. + */ + parser.reportError("msg.mult.index"); + } + if (lastChild.getType() == Token.ARRAYLIT || + lastChild.getType() == Token.OBJECTLIT) + { + type = destructuring = lastChild.getType(); + lvalue = lastChild; + destructuringLen = lastChild.getIntProp( + Node.DESTRUCTURING_ARRAY_LENGTH, 0); + } else if (lastChild.getType() == Token.NAME) { + lvalue = Node.newString(Token.NAME, lastChild.getString()); + } else { + parser.reportError("msg.bad.for.in.lhs"); + return obj; + } + } else if (type == Token.ARRAYLIT || type == Token.OBJECTLIT) { + destructuring = type; + lvalue = lhs; + destructuringLen = lhs.getIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, 0); + } else { + lvalue = makeReference(lhs); + if (lvalue == null) { + parser.reportError("msg.bad.for.in.lhs"); + return obj; + } + } + + Node localBlock = new Node(Token.LOCAL_BLOCK); + int initType = (isForEach) ? Token.ENUM_INIT_VALUES : + (destructuring != -1) ? Token.ENUM_INIT_ARRAY : + Token.ENUM_INIT_KEYS; + Node init = new Node(initType, obj); + init.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + Node cond = new Node(Token.ENUM_NEXT); + cond.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + Node id = new Node(Token.ENUM_ID); + id.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + + Node newBody = new Node(Token.BLOCK); + Node assign; + if (destructuring != -1) { + assign = createDestructuringAssignment(declType, lvalue, id); + if (!isForEach && (destructuring == Token.OBJECTLIT || + destructuringLen != 2)) + { + // destructuring assignment is only allowed in for..each or + // with an array type of length 2 (to hold key and value) + parser.reportError("msg.bad.for.in.destruct"); + } + } else { + assign = simpleAssignment(lvalue, id); + } + newBody.addChildToBack(new Node(Token.EXPR_VOID, assign)); + newBody.addChildToBack(body); + + loop = createWhile(loop, cond, newBody); + loop.addChildToFront(init); + if (type == Token.VAR || type == Token.LET) + loop.addChildToFront(lhs); + localBlock.addChildToBack(loop); + + return localBlock; + } + + /** + * Try/Catch/Finally + * + * The IRFactory tries to express as much as possible in the tree; + * the responsibilities remaining for Codegen are to add the Java + * handlers: (Either (but not both) of TARGET and FINALLY might not + * be defined) + + * - a catch handler for javascript exceptions that unwraps the + * exception onto the stack and GOTOes to the catch target + + * - a finally handler + + * ... and a goto to GOTO around these handlers. + */ + Node createTryCatchFinally(Node tryBlock, Node catchBlocks, + Node finallyBlock, int lineno) + { + boolean hasFinally = (finallyBlock != null) + && (finallyBlock.getType() != Token.BLOCK + || finallyBlock.hasChildren()); + + // short circuit + if (tryBlock.getType() == Token.BLOCK && !tryBlock.hasChildren() + && !hasFinally) + { + return tryBlock; + } + + boolean hasCatch = catchBlocks.hasChildren(); + + // short circuit + if (!hasFinally && !hasCatch) { + // bc finally might be an empty block... + return tryBlock; + } + + + Node handlerBlock = new Node(Token.LOCAL_BLOCK); + Node.Jump pn = new Node.Jump(Token.TRY, tryBlock, lineno); + pn.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); + + if (hasCatch) { + // jump around catch code + Node endCatch = Node.newTarget(); + pn.addChildToBack(makeJump(Token.GOTO, endCatch)); + + // make a TARGET for the catch that the tcf node knows about + Node catchTarget = Node.newTarget(); + pn.target = catchTarget; + // mark it + pn.addChildToBack(catchTarget); + + // + // Given + // + // try { + // tryBlock; + // } catch (e if condition1) { + // something1; + // ... + // + // } catch (e if conditionN) { + // somethingN; + // } catch (e) { + // somethingDefault; + // } + // + // rewrite as + // + // try { + // tryBlock; + // goto after_catch: + // } catch (x) { + // with (newCatchScope(e, x)) { + // if (condition1) { + // something1; + // goto after_catch; + // } + // } + // ... + // with (newCatchScope(e, x)) { + // if (conditionN) { + // somethingN; + // goto after_catch; + // } + // } + // with (newCatchScope(e, x)) { + // somethingDefault; + // goto after_catch; + // } + // } + // after_catch: + // + // If there is no default catch, then the last with block + // arround "somethingDefault;" is replaced by "rethrow;" + + // It is assumed that catch handler generation will store + // exeception object in handlerBlock register + + // Block with local for exception scope objects + Node catchScopeBlock = new Node(Token.LOCAL_BLOCK); + + // expects catchblocks children to be (cond block) pairs. + Node cb = catchBlocks.getFirstChild(); + boolean hasDefault = false; + int scopeIndex = 0; + while (cb != null) { + int catchLineNo = cb.getLineno(); + + Node name = cb.getFirstChild(); + Node cond = name.getNext(); + Node catchStatement = cond.getNext(); + cb.removeChild(name); + cb.removeChild(cond); + cb.removeChild(catchStatement); + + // Add goto to the catch statement to jump out of catch + // but prefix it with LEAVEWITH since try..catch produces + // "with"code in order to limit the scope of the exception + // object. + catchStatement.addChildToBack(new Node(Token.LEAVEWITH)); + catchStatement.addChildToBack(makeJump(Token.GOTO, endCatch)); + + // Create condition "if" when present + Node condStmt; + if (cond.getType() == Token.EMPTY) { + condStmt = catchStatement; + hasDefault = true; + } else { + condStmt = createIf(cond, catchStatement, null, + catchLineNo); + } + + // Generate code to create the scope object and store + // it in catchScopeBlock register + Node catchScope = new Node(Token.CATCH_SCOPE, name, + createUseLocal(handlerBlock)); + catchScope.putProp(Node.LOCAL_BLOCK_PROP, catchScopeBlock); + catchScope.putIntProp(Node.CATCH_SCOPE_PROP, scopeIndex); + catchScopeBlock.addChildToBack(catchScope); + + // Add with statement based on catch scope object + catchScopeBlock.addChildToBack( + createWith(createUseLocal(catchScopeBlock), condStmt, + catchLineNo)); + + // move to next cb + cb = cb.getNext(); + ++scopeIndex; + } + pn.addChildToBack(catchScopeBlock); + if (!hasDefault) { + // Generate code to rethrow if no catch clause was executed + Node rethrow = new Node(Token.RETHROW); + rethrow.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); + pn.addChildToBack(rethrow); + } + + pn.addChildToBack(endCatch); + } + + if (hasFinally) { + Node finallyTarget = Node.newTarget(); + pn.setFinally(finallyTarget); + + // add jsr finally to the try block + pn.addChildToBack(makeJump(Token.JSR, finallyTarget)); + + // jump around finally code + Node finallyEnd = Node.newTarget(); + pn.addChildToBack(makeJump(Token.GOTO, finallyEnd)); + + pn.addChildToBack(finallyTarget); + Node fBlock = new Node(Token.FINALLY, finallyBlock); + fBlock.putProp(Node.LOCAL_BLOCK_PROP, handlerBlock); + pn.addChildToBack(fBlock); + + pn.addChildToBack(finallyEnd); + } + handlerBlock.addChildToBack(pn); + return handlerBlock; + } + + /** + * Throw, Return, Label, Break and Continue are defined in ASTFactory. + */ + + /** + * With + */ + Node createWith(Node obj, Node body, int lineno) + { + setRequiresActivation(); + Node result = new Node(Token.BLOCK, lineno); + result.addChildToBack(new Node(Token.ENTERWITH, obj)); + Node bodyNode = new Node(Token.WITH, body, lineno); + result.addChildrenToBack(bodyNode); + result.addChildToBack(new Node(Token.LEAVEWITH)); + return result; + } + + /** + * DOTQUERY + */ + public Node createDotQuery (Node obj, Node body, int lineno) + { + setRequiresActivation(); + Node result = new Node(Token.DOTQUERY, obj, body, lineno); + return result; + } + + Node createArrayLiteral(ObjArray elems, int skipCount, int destructuringLen) + { + int length = elems.size(); + int[] skipIndexes = null; + if (skipCount != 0) { + skipIndexes = new int[skipCount]; + } + Node array = new Node(Token.ARRAYLIT); + for (int i = 0, j = 0; i != length; ++i) { + Node elem = (Node)elems.get(i); + if (elem != null) { + array.addChildToBack(elem); + } else { + skipIndexes[j] = i; + ++j; + } + } + if (skipCount != 0) { + array.putProp(Node.SKIP_INDEXES_PROP, skipIndexes); + } + array.putIntProp(Node.DESTRUCTURING_ARRAY_LENGTH, destructuringLen); + return array; + } + + /** + * Object Literals + * <BR> createObjectLiteral rewrites its argument as object + * creation plus object property entries, so later compiler + * stages don't need to know about object literals. + */ + Node createObjectLiteral(ObjArray elems) + { + int size = elems.size() / 2; + Node object = new Node(Token.OBJECTLIT); + Object[] properties; + if (size == 0) { + properties = ScriptRuntime.emptyArgs; + } else { + properties = new Object[size]; + for (int i = 0; i != size; ++i) { + properties[i] = elems.get(2 * i); + Node value = (Node)elems.get(2 * i + 1); + object.addChildToBack(value); + } + } + object.putProp(Node.OBJECT_IDS_PROP, properties); + return object; + } + + /** + * Regular expressions + */ + Node createRegExp(int regexpIndex) + { + Node n = new Node(Token.REGEXP); + n.putIntProp(Node.REGEXP_PROP, regexpIndex); + return n; + } + + /** + * If statement + */ + Node createIf(Node cond, Node ifTrue, Node ifFalse, int lineno) + { + int condStatus = isAlwaysDefinedBoolean(cond); + if (condStatus == ALWAYS_TRUE_BOOLEAN) { + return ifTrue; + } else if (condStatus == ALWAYS_FALSE_BOOLEAN) { + if (ifFalse != null) { + return ifFalse; + } + // Replace if (false) xxx by empty block + return new Node(Token.BLOCK, lineno); + } + + Node result = new Node(Token.BLOCK, lineno); + Node ifNotTarget = Node.newTarget(); + Node.Jump IFNE = new Node.Jump(Token.IFNE, cond); + IFNE.target = ifNotTarget; + + result.addChildToBack(IFNE); + result.addChildrenToBack(ifTrue); + + if (ifFalse != null) { + Node endTarget = Node.newTarget(); + result.addChildToBack(makeJump(Token.GOTO, endTarget)); + result.addChildToBack(ifNotTarget); + result.addChildrenToBack(ifFalse); + result.addChildToBack(endTarget); + } else { + result.addChildToBack(ifNotTarget); + } + + return result; + } + + Node createCondExpr(Node cond, Node ifTrue, Node ifFalse) + { + int condStatus = isAlwaysDefinedBoolean(cond); + if (condStatus == ALWAYS_TRUE_BOOLEAN) { + return ifTrue; + } else if (condStatus == ALWAYS_FALSE_BOOLEAN) { + return ifFalse; + } + return new Node(Token.HOOK, cond, ifTrue, ifFalse); + } + + /** + * Unary + */ + Node createUnary(int nodeType, Node child) + { + int childType = child.getType(); + switch (nodeType) { + case Token.DELPROP: { + Node n; + if (childType == Token.NAME) { + // Transform Delete(Name "a") + // to Delete(Bind("a"), String("a")) + child.setType(Token.BINDNAME); + Node left = child; + Node right = Node.newString(child.getString()); + n = new Node(nodeType, left, right); + } else if (childType == Token.GETPROP || + childType == Token.GETELEM) + { + Node left = child.getFirstChild(); + Node right = child.getLastChild(); + child.removeChild(left); + child.removeChild(right); + n = new Node(nodeType, left, right); + } else if (childType == Token.GET_REF) { + Node ref = child.getFirstChild(); + child.removeChild(ref); + n = new Node(Token.DEL_REF, ref); + } else { + n = new Node(Token.TRUE); + } + return n; + } + case Token.TYPEOF: + if (childType == Token.NAME) { + child.setType(Token.TYPEOFNAME); + return child; + } + break; + case Token.BITNOT: + if (childType == Token.NUMBER) { + int value = ScriptRuntime.toInt32(child.getDouble()); + child.setDouble(~value); + return child; + } + break; + case Token.NEG: + if (childType == Token.NUMBER) { + child.setDouble(-child.getDouble()); + return child; + } + break; + case Token.NOT: { + int status = isAlwaysDefinedBoolean(child); + if (status != 0) { + int type; + if (status == ALWAYS_TRUE_BOOLEAN) { + type = Token.FALSE; + } else { + type = Token.TRUE; + } + if (childType == Token.TRUE || childType == Token.FALSE) { + child.setType(type); + return child; + } + return new Node(type); + } + break; + } + } + return new Node(nodeType, child); + } + + Node createYield(Node child, int lineno) + { + if (!parser.insideFunction()) { + parser.reportError("msg.bad.yield"); + } + setRequiresActivation(); + setIsGenerator(); + if (child != null) + return new Node(Token.YIELD, child, lineno); + else + return new Node(Token.YIELD, lineno); + } + + Node createCallOrNew(int nodeType, Node child) + { + int type = Node.NON_SPECIALCALL; + if (child.getType() == Token.NAME) { + String name = child.getString(); + if (name.equals("eval")) { + type = Node.SPECIALCALL_EVAL; + } else if (name.equals("With")) { + type = Node.SPECIALCALL_WITH; + } + } else if (child.getType() == Token.GETPROP) { + String name = child.getLastChild().getString(); + if (name.equals("eval")) { + type = Node.SPECIALCALL_EVAL; + } + } + Node node = new Node(nodeType, child); + if (type != Node.NON_SPECIALCALL) { + // Calls to these functions require activation objects. + setRequiresActivation(); + node.putIntProp(Node.SPECIALCALL_PROP, type); + } + return node; + } + + Node createIncDec(int nodeType, boolean post, Node child) + { + child = makeReference(child); + if (child == null) { + String msg; + if (nodeType == Token.DEC) { + msg = "msg.bad.decr"; + } else { + msg = "msg.bad.incr"; + } + parser.reportError(msg); + return null; + } + + int childType = child.getType(); + + switch (childType) { + case Token.NAME: + case Token.GETPROP: + case Token.GETELEM: + case Token.GET_REF: { + Node n = new Node(nodeType, child); + int incrDecrMask = 0; + if (nodeType == Token.DEC) { + incrDecrMask |= Node.DECR_FLAG; + } + if (post) { + incrDecrMask |= Node.POST_FLAG; + } + n.putIntProp(Node.INCRDECR_PROP, incrDecrMask); + return n; + } + } + throw Kit.codeBug(); + } + + Node createPropertyGet(Node target, String namespace, String name, + int memberTypeFlags) + { + if (namespace == null && memberTypeFlags == 0) { + if (target == null) { + return createName(name); + } + checkActivationName(name, Token.GETPROP); + if (ScriptRuntime.isSpecialProperty(name)) { + Node ref = new Node(Token.REF_SPECIAL, target); + ref.putProp(Node.NAME_PROP, name); + return new Node(Token.GET_REF, ref); + } + return new Node(Token.GETPROP, target, createString(name)); + } + Node elem = createString(name); + memberTypeFlags |= Node.PROPERTY_FLAG; + return createMemberRefGet(target, namespace, elem, memberTypeFlags); + } + + Node createElementGet(Node target, String namespace, Node elem, + int memberTypeFlags) + { + // OPT: could optimize to createPropertyGet + // iff elem is string that can not be number + if (namespace == null && memberTypeFlags == 0) { + // stand-alone [aaa] as primary expression is array literal + // declaration and should not come here! + if (target == null) throw Kit.codeBug(); + return new Node(Token.GETELEM, target, elem); + } + return createMemberRefGet(target, namespace, elem, memberTypeFlags); + } + + private Node createMemberRefGet(Node target, String namespace, Node elem, + int memberTypeFlags) + { + Node nsNode = null; + if (namespace != null) { + // See 11.1.2 in ECMA 357 + if (namespace.equals("*")) { + nsNode = new Node(Token.NULL); + } else { + nsNode = createName(namespace); + } + } + Node ref; + if (target == null) { + if (namespace == null) { + ref = new Node(Token.REF_NAME, elem); + } else { + ref = new Node(Token.REF_NS_NAME, nsNode, elem); + } + } else { + if (namespace == null) { + ref = new Node(Token.REF_MEMBER, target, elem); + } else { + ref = new Node(Token.REF_NS_MEMBER, target, nsNode, elem); + } + } + if (memberTypeFlags != 0) { + ref.putIntProp(Node.MEMBER_TYPE_PROP, memberTypeFlags); + } + return new Node(Token.GET_REF, ref); + } + + /** + * Binary + */ + Node createBinary(int nodeType, Node left, Node right) + { + switch (nodeType) { + + case Token.ADD: + // numerical addition and string concatenation + if (left.type == Token.STRING) { + String s2; + if (right.type == Token.STRING) { + s2 = right.getString(); + } else if (right.type == Token.NUMBER) { + s2 = ScriptRuntime.numberToString(right.getDouble(), 10); + } else { + break; + } + String s1 = left.getString(); + left.setString(s1.concat(s2)); + return left; + } else if (left.type == Token.NUMBER) { + if (right.type == Token.NUMBER) { + left.setDouble(left.getDouble() + right.getDouble()); + return left; + } else if (right.type == Token.STRING) { + String s1, s2; + s1 = ScriptRuntime.numberToString(left.getDouble(), 10); + s2 = right.getString(); + right.setString(s1.concat(s2)); + return right; + } + } + // can't do anything if we don't know both types - since + // 0 + object is supposed to call toString on the object and do + // string concantenation rather than addition + break; + + case Token.SUB: + // numerical subtraction + if (left.type == Token.NUMBER) { + double ld = left.getDouble(); + if (right.type == Token.NUMBER) { + //both numbers + left.setDouble(ld - right.getDouble()); + return left; + } else if (ld == 0.0) { + // first 0: 0-x -> -x + return new Node(Token.NEG, right); + } + } else if (right.type == Token.NUMBER) { + if (right.getDouble() == 0.0) { + //second 0: x - 0 -> +x + // can not make simply x because x - 0 must be number + return new Node(Token.POS, left); + } + } + break; + + case Token.MUL: + // numerical multiplication + if (left.type == Token.NUMBER) { + double ld = left.getDouble(); + if (right.type == Token.NUMBER) { + //both numbers + left.setDouble(ld * right.getDouble()); + return left; + } else if (ld == 1.0) { + // first 1: 1 * x -> +x + return new Node(Token.POS, right); + } + } else if (right.type == Token.NUMBER) { + if (right.getDouble() == 1.0) { + //second 1: x * 1 -> +x + // can not make simply x because x - 0 must be number + return new Node(Token.POS, left); + } + } + // can't do x*0: Infinity * 0 gives NaN, not 0 + break; + + case Token.DIV: + // number division + if (right.type == Token.NUMBER) { + double rd = right.getDouble(); + if (left.type == Token.NUMBER) { + // both constants -- just divide, trust Java to handle x/0 + left.setDouble(left.getDouble() / rd); + return left; + } else if (rd == 1.0) { + // second 1: x/1 -> +x + // not simply x to force number convertion + return new Node(Token.POS, left); + } + } + break; + + case Token.AND: { + // Since x && y gives x, not false, when Boolean(x) is false, + // and y, not Boolean(y), when Boolean(x) is true, x && y + // can only be simplified if x is defined. See bug 309957. + + int leftStatus = isAlwaysDefinedBoolean(left); + if (leftStatus == ALWAYS_FALSE_BOOLEAN) { + // if the first one is false, just return it + return left; + } else if (leftStatus == ALWAYS_TRUE_BOOLEAN) { + // if first is true, set to second + return right; + } + break; + } + + case Token.OR: { + // Since x || y gives x, not true, when Boolean(x) is true, + // and y, not Boolean(y), when Boolean(x) is false, x || y + // can only be simplified if x is defined. See bug 309957. + + int leftStatus = isAlwaysDefinedBoolean(left); + if (leftStatus == ALWAYS_TRUE_BOOLEAN) { + // if the first one is true, just return it + return left; + } else if (leftStatus == ALWAYS_FALSE_BOOLEAN) { + // if first is false, set to second + return right; + } + break; + } + } + + return new Node(nodeType, left, right); + } + + private Node simpleAssignment(Node left, Node right) + { + int nodeType = left.getType(); + switch (nodeType) { + case Token.NAME: + left.setType(Token.BINDNAME); + return new Node(Token.SETNAME, left, right); + + case Token.GETPROP: + case Token.GETELEM: { + Node obj = left.getFirstChild(); + Node id = left.getLastChild(); + int type; + if (nodeType == Token.GETPROP) { + type = Token.SETPROP; + } else { + type = Token.SETELEM; + } + return new Node(type, obj, id, right); + } + case Token.GET_REF: { + Node ref = left.getFirstChild(); + checkMutableReference(ref); + return new Node(Token.SET_REF, ref, right); + } + } + + throw Kit.codeBug(); + } + + private void checkMutableReference(Node n) + { + int memberTypeFlags = n.getIntProp(Node.MEMBER_TYPE_PROP, 0); + if ((memberTypeFlags & Node.DESCENDANTS_FLAG) != 0) { + parser.reportError("msg.bad.assign.left"); + } + } + + Node createAssignment(int assignType, Node left, Node right) + { + Node ref = makeReference(left); + if (ref == null) { + if (left.getType() == Token.ARRAYLIT || + left.getType() == Token.OBJECTLIT) + { + if (assignType != Token.ASSIGN) { + parser.reportError("msg.bad.destruct.op"); + return right; + } + return createDestructuringAssignment(-1, left, right); + } + parser.reportError("msg.bad.assign.left"); + return right; + } + left = ref; + + int assignOp; + switch (assignType) { + case Token.ASSIGN: + return simpleAssignment(left, right); + case Token.ASSIGN_BITOR: assignOp = Token.BITOR; break; + case Token.ASSIGN_BITXOR: assignOp = Token.BITXOR; break; + case Token.ASSIGN_BITAND: assignOp = Token.BITAND; break; + case Token.ASSIGN_LSH: assignOp = Token.LSH; break; + case Token.ASSIGN_RSH: assignOp = Token.RSH; break; + case Token.ASSIGN_URSH: assignOp = Token.URSH; break; + case Token.ASSIGN_ADD: assignOp = Token.ADD; break; + case Token.ASSIGN_SUB: assignOp = Token.SUB; break; + case Token.ASSIGN_MUL: assignOp = Token.MUL; break; + case Token.ASSIGN_DIV: assignOp = Token.DIV; break; + case Token.ASSIGN_MOD: assignOp = Token.MOD; break; + default: throw Kit.codeBug(); + } + + int nodeType = left.getType(); + switch (nodeType) { + case Token.NAME: { + Node op = new Node(assignOp, left, right); + Node lvalueLeft = Node.newString(Token.BINDNAME, left.getString()); + return new Node(Token.SETNAME, lvalueLeft, op); + } + case Token.GETPROP: + case Token.GETELEM: { + Node obj = left.getFirstChild(); + Node id = left.getLastChild(); + + int type = nodeType == Token.GETPROP + ? Token.SETPROP_OP + : Token.SETELEM_OP; + + Node opLeft = new Node(Token.USE_STACK); + Node op = new Node(assignOp, opLeft, right); + return new Node(type, obj, id, op); + } + case Token.GET_REF: { + ref = left.getFirstChild(); + checkMutableReference(ref); + Node opLeft = new Node(Token.USE_STACK); + Node op = new Node(assignOp, opLeft, right); + return new Node(Token.SET_REF_OP, ref, op); + } + } + + throw Kit.codeBug(); + } + + /** + * Given a destructuring assignment with a left hand side parsed + * as an array or object literal and a right hand side expression, + * rewrite as a series of assignments to the variables defined in + * left from property accesses to the expression on the right. + * @param type declaration type: Token.VAR or Token.LET or -1 + * @param left array or object literal containing NAME nodes for + * variables to assign + * @param right expression to assign from + * @return expression that performs a series of assignments to + * the variables defined in left + */ + Node createDestructuringAssignment(int type, Node left, Node right) + { + String tempName = parser.currentScriptOrFn.getNextTempName(); + Node result = destructuringAssignmentHelper(type, left, right, + tempName); + Node comma = result.getLastChild(); + comma.addChildToBack(createName(tempName)); + return result; + } + + private Node destructuringAssignmentHelper(int variableType, Node left, + Node right, String tempName) + { + Node result = createScopeNode(Token.LETEXPR, + parser.getCurrentLineNumber()); + result.addChildToFront(new Node(Token.LET, + createName(Token.NAME, tempName, right))); + try { + parser.pushScope(result); + parser.defineSymbol(Token.LET, tempName); + } finally { + parser.popScope(); + } + Node comma = new Node(Token.COMMA); + result.addChildToBack(comma); + final int setOp = variableType == Token.CONST ? Token.SETCONST + : Token.SETNAME; + List<String> destructuringNames = new ArrayList<String>(); + boolean empty = true; + int type = left.getType(); + if (type == Token.ARRAYLIT) { + int index = 0; + int[] skipIndices = (int[])left.getProp(Node.SKIP_INDEXES_PROP); + int skip = 0; + Node n = left.getFirstChild(); + for (;;) { + if (skipIndices != null) { + while (skip < skipIndices.length && + skipIndices[skip] == index) { + skip++; + index++; + } + } + if (n == null) + break; + Node rightElem = new Node(Token.GETELEM, + createName(tempName), + createNumber(index)); + if (n.getType() == Token.NAME) { + String name = n.getString(); + comma.addChildToBack(new Node(setOp, + createName(Token.BINDNAME, name, null), + rightElem)); + if (variableType != -1) { + parser.defineSymbol(variableType, name); + destructuringNames.add(name); + } + } else { + comma.addChildToBack( + destructuringAssignmentHelper(variableType, n, + rightElem, + parser.currentScriptOrFn.getNextTempName())); + } + index++; + empty = false; + n = n.getNext(); + } + } else if (type == Token.OBJECTLIT) { + int index = 0; + Object[] propertyIds = (Object[]) + left.getProp(Node.OBJECT_IDS_PROP); + for (Node n = left.getFirstChild(); n != null; n = n.getNext()) + { + Object id = propertyIds[index]; + Node rightElem = id instanceof String + ? new Node(Token.GETPROP, + createName(tempName), + createString((String)id)) + : new Node(Token.GETELEM, + createName(tempName), + createNumber(((Number)id).intValue())); + if (n.getType() == Token.NAME) { + String name = n.getString(); + comma.addChildToBack(new Node(setOp, + createName(Token.BINDNAME, name, null), + rightElem)); + if (variableType != -1) { + parser.defineSymbol(variableType, name); + destructuringNames.add(name); + } + } else { + comma.addChildToBack( + destructuringAssignmentHelper(variableType, n, + rightElem, + parser.currentScriptOrFn.getNextTempName())); + } + index++; + empty = false; + } + } else if (type == Token.GETPROP || type == Token.GETELEM) { + comma.addChildToBack(simpleAssignment(left, createName(tempName))); + } else { + parser.reportError("msg.bad.assign.left"); + } + if (empty) { + // Don't want a COMMA node with no children. Just add a zero. + comma.addChildToBack(createNumber(0)); + } + result.putProp(Node.DESTRUCTURING_NAMES, destructuringNames); + return result; + } + + Node createUseLocal(Node localBlock) + { + if (Token.LOCAL_BLOCK != localBlock.getType()) throw Kit.codeBug(); + Node result = new Node(Token.LOCAL_LOAD); + result.putProp(Node.LOCAL_BLOCK_PROP, localBlock); + return result; + } + + private Node.Jump makeJump(int type, Node target) + { + Node.Jump n = new Node.Jump(type); + n.target = target; + return n; + } + + private Node makeReference(Node node) + { + int type = node.getType(); + switch (type) { + case Token.NAME: + case Token.GETPROP: + case Token.GETELEM: + case Token.GET_REF: + return node; + case Token.CALL: + node.setType(Token.REF_CALL); + return new Node(Token.GET_REF, node); + } + // Signal caller to report error + return null; + } + + // Check if Node always mean true or false in boolean context + private static int isAlwaysDefinedBoolean(Node node) + { + switch (node.getType()) { + case Token.FALSE: + case Token.NULL: + return ALWAYS_FALSE_BOOLEAN; + case Token.TRUE: + return ALWAYS_TRUE_BOOLEAN; + case Token.NUMBER: { + double num = node.getDouble(); + if (num == num && num != 0.0) { + return ALWAYS_TRUE_BOOLEAN; + } else { + return ALWAYS_FALSE_BOOLEAN; + } + } + } + return 0; + } + + private void checkActivationName(String name, int token) + { + if (parser.insideFunction()) { + boolean activation = false; + if ("arguments".equals(name) + || (parser.compilerEnv.activationNames != null + && parser.compilerEnv.activationNames.containsKey(name))) + { + activation = true; + } else if ("length".equals(name)) { + if (token == Token.GETPROP + && parser.compilerEnv.getLanguageVersion() + == Context.VERSION_1_2) + { + // Use of "length" in 1.2 requires an activation object. + activation = true; + } + } + if (activation) { + setRequiresActivation(); + } + } + } + + private void setRequiresActivation() + { + if (parser.insideFunction()) { + ((FunctionNode)parser.currentScriptOrFn).itsNeedsActivation = true; + } + } + + private void setIsGenerator() + { + if (parser.insideFunction()) { + ((FunctionNode)parser.currentScriptOrFn).itsIsGenerator = true; + } + } + + private Parser parser; + + private static final int LOOP_DO_WHILE = 0; + private static final int LOOP_WHILE = 1; + private static final int LOOP_FOR = 2; + + private static final int ALWAYS_TRUE_BOOLEAN = 1; + private static final int ALWAYS_FALSE_BOOLEAN = -1; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionCall.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionCall.java new file mode 100644 index 0000000..713fabf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionCall.java @@ -0,0 +1,55 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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; + +/** + * Master for id-based functions that knows their properties and how to + * execute them. + */ +public interface IdFunctionCall +{ + /** + * 'thisObj' will be null if invoked as constructor, in which case + * instance of Scriptable should be returned + */ + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args); + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionObject.java new file mode 100644 index 0000000..c578dfa --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdFunctionObject.java @@ -0,0 +1,189 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +public class IdFunctionObject extends BaseFunction +{ + + static final long serialVersionUID = -5332312783643935019L; + + public IdFunctionObject(IdFunctionCall idcall, Object tag, int id, int arity) + { + if (arity < 0) + throw new IllegalArgumentException(); + + this.idcall = idcall; + this.tag = tag; + this.methodId = id; + this.arity = arity; + if (arity < 0) throw new IllegalArgumentException(); + } + + public IdFunctionObject(IdFunctionCall idcall, Object tag, int id, + String name, int arity, Scriptable scope) + { + super(scope, null); + + if (arity < 0) + throw new IllegalArgumentException(); + if (name == null) + throw new IllegalArgumentException(); + + this.idcall = idcall; + this.tag = tag; + this.methodId = id; + this.arity = arity; + this.functionName = name; + } + + public void initFunction(String name, Scriptable scope) + { + if (name == null) throw new IllegalArgumentException(); + if (scope == null) throw new IllegalArgumentException(); + this.functionName = name; + setParentScope(scope); + } + + public final boolean hasTag(Object tag) + { + return this.tag == tag; + } + + public final int methodId() + { + return methodId; + } + + public final void markAsConstructor(Scriptable prototypeProperty) + { + useCallAsConstructor = true; + setImmunePrototypeProperty(prototypeProperty); + } + + public final void addAsProperty(Scriptable target) + { + ScriptableObject.defineProperty(target, functionName, this, + ScriptableObject.DONTENUM); + } + + public void exportAsScopeProperty() + { + addAsProperty(getParentScope()); + } + + public Scriptable getPrototype() + { + // Lazy initialization of prototype: for native functions this + // may not be called at all + Scriptable proto = super.getPrototype(); + if (proto == null) { + proto = getFunctionPrototype(getParentScope()); + setPrototype(proto); + } + return proto; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return idcall.execIdCall(this, cx, scope, thisObj, args); + } + + public Scriptable createObject(Context cx, Scriptable scope) + { + if (useCallAsConstructor) { + return null; + } + // Throw error if not explicitly coded to be used as constructor, + // to satisfy ECMAScript standard (see bugzilla 202019). + // To follow current (2003-05-01) SpiderMonkey behavior, change it to: + // return super.createObject(cx, scope); + throw ScriptRuntime.typeError1("msg.not.ctor", functionName); + } + + String decompile(int indent, int flags) + { + StringBuffer sb = new StringBuffer(); + boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() { "); + } + sb.append("[native code for "); + if (idcall instanceof Scriptable) { + Scriptable sobj = (Scriptable)idcall; + sb.append(sobj.getClassName()); + sb.append('.'); + } + sb.append(getFunctionName()); + sb.append(", arity="); + sb.append(getArity()); + sb.append(justbody ? "]\n" : "] }\n"); + return sb.toString(); + } + + public int getArity() + { + return arity; + } + + public int getLength() { return getArity(); } + + public String getFunctionName() + { + return (functionName == null) ? "" : functionName; + } + + public final RuntimeException unknown() + { + // It is program error to call id-like methods for unknown function + return new IllegalArgumentException( + "BAD FUNCTION ID="+methodId+" MASTER="+idcall); + } + + private final IdFunctionCall idcall; + private final Object tag; + private final int methodId; + private int arity; + private boolean useCallAsConstructor; + private String functionName; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdScriptableObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdScriptableObject.java new file mode 100644 index 0000000..2b3ecf3 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/IdScriptableObject.java @@ -0,0 +1,734 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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.io.*; + +/** +Base class for native object implementation that uses IdFunctionObject to export its methods to script via <class-name>.prototype object. + +Any descendant should implement at least the following methods: + findInstanceIdInfo + getInstanceIdName + execIdCall + methodArity + +To define non-function properties, the descendant should override + getInstanceIdValue + setInstanceIdValue +to get/set property value and provide its default attributes. + + +To customize initializition of constructor and protype objects, descendant +may override scopeInit or fillConstructorProperties methods. + +*/ +public abstract class IdScriptableObject extends ScriptableObject + implements IdFunctionCall +{ + private transient volatile PrototypeValues prototypeValues; + + private static final class PrototypeValues implements Serializable + { + static final long serialVersionUID = 3038645279153854371L; + + private static final int VALUE_SLOT = 0; + private static final int NAME_SLOT = 1; + private static final int SLOT_SPAN = 2; + + private IdScriptableObject obj; + private int maxId; + private volatile Object[] valueArray; + private volatile short[] attributeArray; + private volatile int lastFoundId = 1; + + // The following helps to avoid creation of valueArray during runtime + // initialization for common case of "constructor" property + int constructorId; + private IdFunctionObject constructor; + private short constructorAttrs; + + PrototypeValues(IdScriptableObject obj, int maxId) + { + if (obj == null) throw new IllegalArgumentException(); + if (maxId < 1) throw new IllegalArgumentException(); + this.obj = obj; + this.maxId = maxId; + } + + final int getMaxId() + { + return maxId; + } + + final void initValue(int id, String name, Object value, int attributes) + { + if (!(1 <= id && id <= maxId)) + throw new IllegalArgumentException(); + if (name == null) + throw new IllegalArgumentException(); + if (value == NOT_FOUND) + throw new IllegalArgumentException(); + ScriptableObject.checkValidAttributes(attributes); + if (obj.findPrototypeId(name) != id) + throw new IllegalArgumentException(name); + + if (id == constructorId) { + if (!(value instanceof IdFunctionObject)) { + throw new IllegalArgumentException("consructor should be initialized with IdFunctionObject"); + } + constructor = (IdFunctionObject)value; + constructorAttrs = (short)attributes; + return; + } + + initSlot(id, name, value, attributes); + } + + private void initSlot(int id, String name, Object value, + int attributes) + { + Object[] array = valueArray; + if (array == null) + throw new IllegalStateException(); + + if (value == null) { + value = UniqueTag.NULL_VALUE; + } + int index = (id - 1) * SLOT_SPAN; + synchronized (this) { + Object value2 = array[index + VALUE_SLOT]; + if (value2 == null) { + array[index + VALUE_SLOT] = value; + array[index + NAME_SLOT] = name; + attributeArray[id - 1] = (short)attributes; + } else { + if (!name.equals(array[index + NAME_SLOT])) + throw new IllegalStateException(); + } + } + } + + final IdFunctionObject createPrecachedConstructor() + { + if (constructorId != 0) throw new IllegalStateException(); + constructorId = obj.findPrototypeId("constructor"); + if (constructorId == 0) { + throw new IllegalStateException( + "No id for constructor property"); + } + obj.initPrototypeId(constructorId); + if (constructor == null) { + throw new IllegalStateException( + obj.getClass().getName()+".initPrototypeId() did not " + +"initialize id="+constructorId); + } + constructor.initFunction(obj.getClassName(), + ScriptableObject.getTopLevelScope(obj)); + constructor.markAsConstructor(obj); + return constructor; + } + + final int findId(String name) + { + Object[] array = valueArray; + if (array == null) { + return obj.findPrototypeId(name); + } + int id = lastFoundId; + if (name == array[(id - 1) * SLOT_SPAN + NAME_SLOT]) { + return id; + } + id = obj.findPrototypeId(name); + if (id != 0) { + int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT; + // Make cache to work! + array[nameSlot] = name; + lastFoundId = id; + } + return id; + } + + final boolean has(int id) + { + Object[] array = valueArray; + if (array == null) { + // Not yet initialized, assume all exists + return true; + } + int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT; + Object value = array[valueSlot]; + if (value == null) { + // The particular entry has not been yet initialized + return true; + } + return value != NOT_FOUND; + } + + final Object get(int id) + { + Object value = ensureId(id); + if (value == UniqueTag.NULL_VALUE) { + value = null; + } + return value; + } + + final void set(int id, Scriptable start, Object value) + { + if (value == NOT_FOUND) throw new IllegalArgumentException(); + ensureId(id); + int attr = attributeArray[id - 1]; + if ((attr & READONLY) == 0) { + if (start == obj) { + if (value == null) { + value = UniqueTag.NULL_VALUE; + } + int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT; + synchronized (this) { + valueArray[valueSlot] = value; + } + } + else { + int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT; + String name = (String)valueArray[nameSlot]; + start.put(name, start, value); + } + } + } + + final void delete(int id) + { + ensureId(id); + int attr = attributeArray[id - 1]; + if ((attr & PERMANENT) == 0) { + int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT; + synchronized (this) { + valueArray[valueSlot] = NOT_FOUND; + attributeArray[id - 1] = EMPTY; + } + } + } + + final int getAttributes(int id) + { + ensureId(id); + return attributeArray[id - 1]; + } + + final void setAttributes(int id, int attributes) + { + ScriptableObject.checkValidAttributes(attributes); + ensureId(id); + synchronized (this) { + attributeArray[id - 1] = (short)attributes; + } + } + + final Object[] getNames(boolean getAll, Object[] extraEntries) + { + Object[] names = null; + int count = 0; + for (int id = 1; id <= maxId; ++id) { + Object value = ensureId(id); + if (getAll || (attributeArray[id - 1] & DONTENUM) == 0) { + if (value != NOT_FOUND) { + int nameSlot = (id - 1) * SLOT_SPAN + NAME_SLOT; + String name = (String)valueArray[nameSlot]; + if (names == null) { + names = new Object[maxId]; + } + names[count++] = name; + } + } + } + if (count == 0) { + return extraEntries; + } else if (extraEntries == null || extraEntries.length == 0) { + if (count != names.length) { + Object[] tmp = new Object[count]; + System.arraycopy(names, 0, tmp, 0, count); + names = tmp; + } + return names; + } else { + int extra = extraEntries.length; + Object[] tmp = new Object[extra + count]; + System.arraycopy(extraEntries, 0, tmp, 0, extra); + System.arraycopy(names, 0, tmp, extra, count); + return tmp; + } + } + + private Object ensureId(int id) + { + Object[] array = valueArray; + if (array == null) { + synchronized (this) { + array = valueArray; + if (array == null) { + array = new Object[maxId * SLOT_SPAN]; + valueArray = array; + attributeArray = new short[maxId]; + } + } + } + int valueSlot = (id - 1) * SLOT_SPAN + VALUE_SLOT; + Object value = array[valueSlot]; + if (value == null) { + if (id == constructorId) { + initSlot(constructorId, "constructor", + constructor, constructorAttrs); + constructor = null; // no need to refer it any longer + } else { + obj.initPrototypeId(id); + } + value = array[valueSlot]; + if (value == null) { + throw new IllegalStateException( + obj.getClass().getName()+".initPrototypeId(int id) " + +"did not initialize id="+id); + } + } + return value; + } + } + + public IdScriptableObject() + { + } + + public IdScriptableObject(Scriptable scope, Scriptable prototype) + { + super(scope, prototype); + } + + protected final Object defaultGet(String name) + { + return super.get(name, this); + } + + protected final void defaultPut(String name, Object value) + { + super.put(name, this, value); + } + + public boolean has(String name, Scriptable start) + { + int info = findInstanceIdInfo(name); + if (info != 0) { + int attr = (info >>> 16); + if ((attr & PERMANENT) != 0) { + return true; + } + int id = (info & 0xFFFF); + return NOT_FOUND != getInstanceIdValue(id); + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + return prototypeValues.has(id); + } + } + return super.has(name, start); + } + + public Object get(String name, Scriptable start) + { + int info = findInstanceIdInfo(name); + if (info != 0) { + int id = (info & 0xFFFF); + return getInstanceIdValue(id); + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + return prototypeValues.get(id); + } + } + return super.get(name, start); + } + + public void put(String name, Scriptable start, Object value) + { + int info = findInstanceIdInfo(name); + if (info != 0) { + if (start == this && isSealed()) { + throw Context.reportRuntimeError1("msg.modify.sealed", + name); + } + int attr = (info >>> 16); + if ((attr & READONLY) == 0) { + if (start == this) { + int id = (info & 0xFFFF); + setInstanceIdValue(id, value); + } + else { + start.put(name, start, value); + } + } + return; + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + if (start == this && isSealed()) { + throw Context.reportRuntimeError1("msg.modify.sealed", + name); + } + prototypeValues.set(id, start, value); + return; + } + } + super.put(name, start, value); + } + + public void delete(String name) + { + int info = findInstanceIdInfo(name); + if (info != 0) { + // Let the super class to throw exceptions for sealed objects + if (!isSealed()) { + int attr = (info >>> 16); + if ((attr & PERMANENT) == 0) { + int id = (info & 0xFFFF); + setInstanceIdValue(id, NOT_FOUND); + } + return; + } + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + if (!isSealed()) { + prototypeValues.delete(id); + } + return; + } + } + super.delete(name); + } + + public int getAttributes(String name) + { + int info = findInstanceIdInfo(name); + if (info != 0) { + int attr = (info >>> 16); + return attr; + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + return prototypeValues.getAttributes(id); + } + } + return super.getAttributes(name); + } + + public void setAttributes(String name, int attributes) + { + ScriptableObject.checkValidAttributes(attributes); + int info = findInstanceIdInfo(name); + if (info != 0) { + int currentAttributes = (info >>> 16); + if (attributes != currentAttributes) { + throw new RuntimeException( + "Change of attributes for this id is not supported"); + } + return; + } + if (prototypeValues != null) { + int id = prototypeValues.findId(name); + if (id != 0) { + prototypeValues.setAttributes(id, attributes); + return; + } + } + super.setAttributes(name, attributes); + } + + Object[] getIds(boolean getAll) + { + Object[] result = super.getIds(getAll); + + if (prototypeValues != null) { + result = prototypeValues.getNames(getAll, result); + } + + int maxInstanceId = getMaxInstanceId(); + if (maxInstanceId != 0) { + Object[] ids = null; + int count = 0; + + for (int id = maxInstanceId; id != 0; --id) { + String name = getInstanceIdName(id); + int info = findInstanceIdInfo(name); + if (info != 0) { + int attr = (info >>> 16); + if ((attr & PERMANENT) == 0) { + if (NOT_FOUND == getInstanceIdValue(id)) { + continue; + } + } + if (getAll || (attr & DONTENUM) == 0) { + if (count == 0) { + // Need extra room for no more then [1..id] names + ids = new Object[id]; + } + ids[count++] = name; + } + } + } + if (count != 0) { + if (result.length == 0 && ids.length == count) { + result = ids; + } + else { + Object[] tmp = new Object[result.length + count]; + System.arraycopy(result, 0, tmp, 0, result.length); + System.arraycopy(ids, 0, tmp, result.length, count); + result = tmp; + } + } + } + return result; + } + + /** + * Get maximum id findInstanceIdInfo can generate. + */ + protected int getMaxInstanceId() + { + return 0; + } + + protected static int instanceIdInfo(int attributes, int id) + { + return (attributes << 16) | id; + } + + /** + * Map name to id of instance property. + * Should return 0 if not found or the result of + * {@link #instanceIdInfo(int, int)}. + */ + protected int findInstanceIdInfo(String name) + { + return 0; + } + + /** Map id back to property name it defines. + */ + protected String getInstanceIdName(int id) + { + throw new IllegalArgumentException(String.valueOf(id)); + } + + /** Get id value. + ** If id value is constant, descendant can call cacheIdValue to store + ** value in the permanent cache. + ** Default implementation creates IdFunctionObject instance for given id + ** and cache its value + */ + protected Object getInstanceIdValue(int id) + { + throw new IllegalStateException(String.valueOf(id)); + } + + /** + * Set or delete id value. If value == NOT_FOUND , the implementation + * should make sure that the following getInstanceIdValue return NOT_FOUND. + */ + protected void setInstanceIdValue(int id, Object value) + { + throw new IllegalStateException(String.valueOf(id)); + } + + /** 'thisObj' will be null if invoked as constructor, in which case + ** instance of Scriptable should be returned. */ + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + throw f.unknown(); + } + + public final IdFunctionObject exportAsJSClass(int maxPrototypeId, + Scriptable scope, + boolean sealed) + { + // Set scope and prototype unless this is top level scope itself + if (scope != this && scope != null) { + setParentScope(scope); + setPrototype(getObjectPrototype(scope)); + } + + activatePrototypeMap(maxPrototypeId); + IdFunctionObject ctor = prototypeValues.createPrecachedConstructor(); + if (sealed) { + sealObject(); + } + fillConstructorProperties(ctor); + if (sealed) { + ctor.sealObject(); + } + ctor.exportAsScopeProperty(); + return ctor; + } + + public final boolean hasPrototypeMap() + { + return prototypeValues != null; + } + + public final void activatePrototypeMap(int maxPrototypeId) + { + PrototypeValues values = new PrototypeValues(this, maxPrototypeId); + synchronized (this) { + if (prototypeValues != null) + throw new IllegalStateException(); + prototypeValues = values; + } + } + + public final void initPrototypeMethod(Object tag, int id, String name, + int arity) + { + Scriptable scope = ScriptableObject.getTopLevelScope(this); + IdFunctionObject f = newIdFunction(tag, id, name, arity, scope); + prototypeValues.initValue(id, name, f, DONTENUM); + } + + public final void initPrototypeConstructor(IdFunctionObject f) + { + int id = prototypeValues.constructorId; + if (id == 0) + throw new IllegalStateException(); + if (f.methodId() != id) + throw new IllegalArgumentException(); + if (isSealed()) { f.sealObject(); } + prototypeValues.initValue(id, "constructor", f, DONTENUM); + } + + public final void initPrototypeValue(int id, String name, Object value, + int attributes) + { + prototypeValues.initValue(id, name, value, attributes); + } + + protected void initPrototypeId(int id) + { + throw new IllegalStateException(String.valueOf(id)); + } + + protected int findPrototypeId(String name) + { + throw new IllegalStateException(name); + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + } + + protected void addIdFunctionProperty(Scriptable obj, Object tag, int id, + String name, int arity) + { + Scriptable scope = ScriptableObject.getTopLevelScope(obj); + IdFunctionObject f = newIdFunction(tag, id, name, arity, scope); + f.addAsProperty(obj); + } + + /** + * Utility method to construct type error to indicate incompatible call + * when converting script thisObj to a particular type is not possible. + * Possible usage would be to have a private function like realThis: + * <pre> + * private static NativeSomething realThis(Scriptable thisObj, + * IdFunctionObject f) + * { + * if (!(thisObj instanceof NativeSomething)) + * throw incompatibleCallError(f); + * return (NativeSomething)thisObj; + * } + * </pre> + * Note that although such function can be implemented universally via + * java.lang.Class.isInstance(), it would be much more slower. + * @param f function that is attempting to convert 'this' + * object. + * @return Scriptable object suitable for a check by the instanceof + * operator. + * @throws RuntimeException if no more instanceof target can be found + */ + protected static EcmaError incompatibleCallError(IdFunctionObject f) + { + throw ScriptRuntime.typeError1("msg.incompat.call", + f.getFunctionName()); + } + + private IdFunctionObject newIdFunction(Object tag, int id, String name, + int arity, Scriptable scope) + { + IdFunctionObject f = new IdFunctionObject(this, tag, id, name, arity, + scope); + if (isSealed()) { f.sealObject(); } + return f; + } + + private void readObject(ObjectInputStream stream) + throws IOException, ClassNotFoundException + { + stream.defaultReadObject(); + int maxPrototypeId = stream.readInt(); + if (maxPrototypeId != 0) { + activatePrototypeMap(maxPrototypeId); + } + } + + private void writeObject(ObjectOutputStream stream) + throws IOException + { + stream.defaultWriteObject(); + int maxPrototypeId = 0; + if (prototypeValues != null) { + maxPrototypeId = prototypeValues.getMaxId(); + } + stream.writeInt(maxPrototypeId); + } + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ImporterTopLevel.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ImporterTopLevel.java new file mode 100644 index 0000000..294deab --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ImporterTopLevel.java @@ -0,0 +1,318 @@ +/* -*- 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) 1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Matthias Radestock + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Class ImporterTopLevel + * + * This class defines a ScriptableObject that can be instantiated + * as a top-level ("global") object to provide functionality similar + * to Java's "import" statement. + * <p> + * This class can be used to create a top-level scope using the following code: + * <pre> + * Scriptable scope = new ImporterTopLevel(cx); + * </pre> + * Then JavaScript code will have access to the following methods: + * <ul> + * <li>importClass - will "import" a class by making its unqualified name + * available as a property of the top-level scope + * <li>importPackage - will "import" all the classes of the package by + * searching for unqualified names as classes qualified + * by the given package. + * </ul> + * The following code from the shell illustrates this use: + * <pre> + * js> importClass(java.io.File) + * js> f = new File('help.txt') + * help.txt + * js> importPackage(java.util) + * js> v = new Vector() + * [] + * + * @author Norris Boyd + */ +public class ImporterTopLevel extends IdScriptableObject +{ + static final long serialVersionUID = -9095380847465315412L; + + private static final Object IMPORTER_TAG = new Object(); + + public ImporterTopLevel() { } + + public ImporterTopLevel(Context cx) { + this(cx, false); + } + + public ImporterTopLevel(Context cx, boolean sealed) + { + initStandardObjects(cx, sealed); + } + + public String getClassName() + { + return (topScopeFlag) ? "global" : "JavaImporter"; + } + + public static void init(Context cx, Scriptable scope, boolean sealed) + { + ImporterTopLevel obj = new ImporterTopLevel(); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + public void initStandardObjects(Context cx, boolean sealed) + { + // Assume that Context.initStandardObjects initialize JavaImporter + // property lazily so the above init call is not yet called + cx.initStandardObjects(this, sealed); + topScopeFlag = true; + // If seal is true then exportAsJSClass(cx, seal) would seal + // this obj. Since this is scope as well, it would not allow + // to add variables. + IdFunctionObject ctor = exportAsJSClass(MAX_PROTOTYPE_ID, this, false); + if (sealed) { + ctor.sealObject(); + } + // delete "constructor" defined by exportAsJSClass so "constructor" + // name would refer to Object.constructor + // and not to JavaImporter.prototype.constructor. + delete("constructor"); + } + + public boolean has(String name, Scriptable start) { + return super.has(name, start) + || getPackageProperty(name, start) != NOT_FOUND; + } + + public Object get(String name, Scriptable start) { + Object result = super.get(name, start); + if (result != NOT_FOUND) + return result; + result = getPackageProperty(name, start); + return result; + } + + private Object getPackageProperty(String name, Scriptable start) { + Object result = NOT_FOUND; + Object[] elements; + synchronized (importedPackages) { + elements = importedPackages.toArray(); + } + for (int i=0; i < elements.length; i++) { + NativeJavaPackage p = (NativeJavaPackage) elements[i]; + Object v = p.getPkgProperty(name, start, false); + if (v != null && !(v instanceof NativeJavaPackage)) { + if (result == NOT_FOUND) { + result = v; + } else { + throw Context.reportRuntimeError2( + "msg.ambig.import", result.toString(), v.toString()); + } + } + } + return result; + } + + /** + * @deprecated Kept only for compatibility. + */ + public void importPackage(Context cx, Scriptable thisObj, Object[] args, + Function funObj) + { + js_importPackage(args); + } + + private Object js_construct(Scriptable scope, Object[] args) + { + ImporterTopLevel result = new ImporterTopLevel(); + for (int i = 0; i != args.length; ++i) { + Object arg = args[i]; + if (arg instanceof NativeJavaClass) { + result.importClass((NativeJavaClass)arg); + } else if (arg instanceof NativeJavaPackage) { + result.importPackage((NativeJavaPackage)arg); + } else { + throw Context.reportRuntimeError1( + "msg.not.class.not.pkg", Context.toString(arg)); + } + } + // set explicitly prototype and scope + // as otherwise in top scope mode BaseFunction.construct + // would keep them set to null. It also allow to use + // JavaImporter without new and still get properly + // initialized object. + result.setParentScope(scope); + result.setPrototype(this); + return result; + } + + private Object js_importClass(Object[] args) + { + for (int i = 0; i != args.length; i++) { + Object arg = args[i]; + if (!(arg instanceof NativeJavaClass)) { + throw Context.reportRuntimeError1( + "msg.not.class", Context.toString(arg)); + } + importClass((NativeJavaClass)arg); + } + return Undefined.instance; + } + + private Object js_importPackage(Object[] args) + { + for (int i = 0; i != args.length; i++) { + Object arg = args[i]; + if (!(arg instanceof NativeJavaPackage)) { + throw Context.reportRuntimeError1( + "msg.not.pkg", Context.toString(arg)); + } + importPackage((NativeJavaPackage)arg); + } + return Undefined.instance; + } + + private void importPackage(NativeJavaPackage pkg) + { + if(pkg == null) { + return; + } + synchronized (importedPackages) { + for (int j = 0; j != importedPackages.size(); j++) { + if (pkg.equals(importedPackages.get(j))) { + return; + } + } + importedPackages.add(pkg); + } + } + + private void importClass(NativeJavaClass cl) + { + String s = cl.getClassObject().getName(); + String n = s.substring(s.lastIndexOf('.')+1); + Object val = get(n, this); + if (val != NOT_FOUND && val != cl) { + throw Context.reportRuntimeError1("msg.prop.defined", n); + } + //defineProperty(n, cl, DONTENUM); + put(n, this, cl); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=0; s="constructor"; break; + case Id_importClass: arity=1; s="importClass"; break; + case Id_importPackage: arity=1; s="importPackage"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(IMPORTER_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(IMPORTER_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: + return js_construct(scope, args); + + case Id_importClass: + return realThis(thisObj, f).js_importClass(args); + + case Id_importPackage: + return realThis(thisObj, f).js_importPackage(args); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private ImporterTopLevel realThis(Scriptable thisObj, IdFunctionObject f) + { + if (topScopeFlag) { + // when used as top scope importPackage and importClass are global + // function that ignore thisObj + return this; + } + if (!(thisObj instanceof ImporterTopLevel)) + throw incompatibleCallError(f); + return (ImporterTopLevel)thisObj; + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:24 EDT + L0: { id = 0; String X = null; int c; + int s_length = s.length(); + if (s_length==11) { + c=s.charAt(0); + if (c=='c') { X="constructor";id=Id_constructor; } + else if (c=='i') { X="importClass";id=Id_importClass; } + } + else if (s_length==13) { X="importPackage";id=Id_importPackage; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_importClass = 2, + Id_importPackage = 3, + MAX_PROTOTYPE_ID = 3; + +// #/string_id_map# + + private ObjArray importedPackages = new ObjArray(); + private boolean topScopeFlag; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InformativeParser.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InformativeParser.java new file mode 100644 index 0000000..c73db34 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InformativeParser.java @@ -0,0 +1,225 @@ +package org.mozilla.javascript; + +import java.io.IOException; + +/** + * Subclass of Rhino's Parser that saves information about the token stream + * and error message to allow more helpful error messages. + * + * @author David Greenspan for AppJet + */ + +/* + This class is written with speed in mind, to some extent. Rhino's tokenizer + is pretty efficient, and we wouldn't want to slow it down by, for example, + creating a TokenInfo object on the heap for every token seen. + */ + +/*APPJET*/ +public class InformativeParser extends Parser { + + public static class InformativeEvaluatorException extends EvaluatorException { + final ParseErrorInfo pei; + + InformativeEvaluatorException(String errorMessage, + String sourceName, int lineNumber, + String lineSource, int columnNumber, + ParseErrorInfo peInfo) { + super(errorMessage, sourceName, + lineNumber, lineSource, columnNumber); + pei = peInfo; + } + + public ParseErrorInfo getParseErrorInfo() { + return pei; + } + } + + public static class ParseErrorInfo { + ParseErrorInfo() {} + + String messageId = null; + String messageArg = null; + + final int tokenMaxHistory = 10; + // ring buffers + final int[] tokenTypes = new int[tokenMaxHistory]; + final String[] tokenStrings = new String[tokenMaxHistory]; + final double[] tokenNumbers = new double[tokenMaxHistory]; + final int[] tokenLineNumbers = new int[tokenMaxHistory]; + final int[] tokenLineOffsets = new int[tokenMaxHistory]; + int nextBufPos = 0; + int historyLength = 0; + boolean tokenPeeking = false; + int peekSlot; + + void reportPeekToken(int type, String str, double num, int lineno, + int lineOffset) { + if (! tokenPeeking) { + peekSlot = nextBufPos; + tokenTypes[nextBufPos] = type; + tokenStrings[nextBufPos] = str; + tokenNumbers[nextBufPos] = num; + tokenLineNumbers[nextBufPos] = lineno; + tokenLineOffsets[nextBufPos] = lineOffset; + + nextBufPos++; + if (nextBufPos == tokenMaxHistory) nextBufPos = 0; + if (historyLength < tokenMaxHistory) historyLength++; + tokenPeeking = true; + } + } + + void reportConsumeToken() { + tokenPeeking = false; + } + + private TokenInfo backToken(int n) { + // 0 is most recent token added to history + if (n >= historyLength) return null; + int i = (nextBufPos - 1 - n); + while (i < 0) i += tokenMaxHistory; + return new TokenInfo(tokenTypes[i], tokenStrings[i], + tokenNumbers[i], tokenLineNumbers[i], + tokenLineOffsets[i]); + } + + public String getMessageId() { return messageId; } + public String getMessageArg() { return messageArg; } + public TokenInfo getPeekToken() { + if (tokenPeeking) return backToken(0); + return null; + } + public TokenInfo getPrevToken(int n) { + // 1 = last non-peek token seen, 2 = before that, etc. + if (! tokenPeeking) n--; + return backToken(n); + } + public TokenInfo getPrevToken() { + return getPrevToken(1); + } + } + + public static class TokenInfo { + private int type, lineno, lineOffset; + private String str; + private double num; + TokenInfo(int type, String str, double num, int lineno, + int lineOffset) { + this.type = type; this.str = str; this.num = num; + this.lineno = lineno; this.lineOffset = lineOffset; + } + public int getType() { return type; } + public int getLineNumber() { return lineno; } + public int getLineOffset() { return lineOffset; } + public double getNumber() { return num; } + public String getString() { return str; } + } + + ParseErrorInfo info = new ParseErrorInfo(); + + void doErrorReporterError(String message, String sourceURI, int line, + String lineText, int lineOffset) { + + throw new InformativeEvaluatorException(message, sourceURI, line, + lineText, lineOffset, info); + + } + + public InformativeParser(CompilerEnvirons compilerEnv) { + // we override most calls to the parent's ErrorReporter anyway + super(compilerEnv, DefaultErrorReporter.instance); + } + + @Override int peekToken() throws IOException { + int tt = super.peekToken(); + info.reportPeekToken(tt, ts.getString(), ts.getNumber(), + ts.getLineno(), ts.getOffset()); + return tt; + } + @Override void consumeToken() { + super.consumeToken(); + info.reportConsumeToken(); + } + + @Override void addWarning(String messageId, String messageArg) + { + info.messageId = messageId; + info.messageArg = messageArg; + + String message = ScriptRuntime.getMessage1(messageId, messageArg); + if (compilerEnv.reportWarningAsError()) { + ++syntaxErrorCount; + doErrorReporterError(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + else { /* don't report */ } + } + + @Override void addError(String messageId) + { + info.messageId = messageId; + + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage0(messageId); + doErrorReporterError(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + @Override void addError(String messageId, String messageArg) + { + info.messageId = messageId; + info.messageArg = messageArg; + + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage1(messageId, messageArg); + doErrorReporterError(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + @Override protected Decompiler createDecompiler(CompilerEnvirons env) { + return new MyDecompiler(); + } + + public static final ErrorReporter THROW_INFORMATIVE_ERRORS + = new ErrorReporter() { + public void warning(String message, String sourceURI, int line, + String lineText, int lineOffset) { + DefaultErrorReporter.instance.warning + (message, sourceURI, line, lineText, lineOffset); + } + public void error(String message, String sourceURI, int line, + String lineText, int lineOffset) { + DefaultErrorReporter.instance.error + (message, sourceURI, line, lineText, lineOffset); + } + public EvaluatorException runtimeError(String message, + String sourceURI, + int line, String lineText, + int lineOffset) { + return DefaultErrorReporter.instance.runtimeError + (message, sourceURI, line, lineText, lineOffset); + } + + }; + + public static Parser makeParser(CompilerEnvirons compilerEnv, + ErrorReporter errorReporter) { + if (errorReporter == THROW_INFORMATIVE_ERRORS) { + return new InformativeParser(compilerEnv); + } + else { + return new Parser(compilerEnv, errorReporter); + } + } + + private class MyDecompiler extends Decompiler { + @Override void addRegexp(String regexp, String flags) { + super.addRegexp(regexp, flags); + String str = '/'+regexp+'/'+flags; + info.reportPeekToken(Token.REGEXP, str, ts.getNumber(), + ts.getLineno(), ts.getOffset()); + info.reportConsumeToken(); + } + } +}
\ No newline at end of file diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterfaceAdapter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterfaceAdapter.java new file mode 100644 index 0000000..877e6a2 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterfaceAdapter.java @@ -0,0 +1,156 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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.Method; + +/** + * Adapter to use JS function as implementation of Java interfaces with + * single method or multiple methods with the same signature. + */ +public class InterfaceAdapter +{ + private final Object proxyHelper; + + /** + * Make glue object implementing interface cl that will + * call the supplied JS function when called. + * Only interfaces were all methods have the same signature is supported. + * + * @return The glue object or null if <tt>cl</tt> is not interface or + * has methods with different signatures. + */ + static Object create(Context cx, Class cl, Callable function) + { + if (!cl.isInterface()) throw new IllegalArgumentException(); + + Scriptable topScope = ScriptRuntime.getTopCallScope(cx); + ClassCache cache = ClassCache.get(topScope); + InterfaceAdapter adapter; + adapter = (InterfaceAdapter)cache.getInterfaceAdapter(cl); + ContextFactory cf = cx.getFactory(); + if (adapter == null) { + Method[] methods = cl.getMethods(); + if (methods.length == 0) { + throw Context.reportRuntimeError2( + "msg.no.empty.interface.conversion", + String.valueOf(function), + cl.getClass().getName()); + } + boolean canCallFunction = false; + canCallFunctionChecks: { + Class[] argTypes = methods[0].getParameterTypes(); + // check that the rest of methods has the same signature + for (int i = 1; i != methods.length; ++i) { + Class[] types2 = methods[i].getParameterTypes(); + if (types2.length != argTypes.length) { + break canCallFunctionChecks; + } + for (int j = 0; j != argTypes.length; ++j) { + if (types2[j] != argTypes[j]) { + break canCallFunctionChecks; + } + } + } + canCallFunction= true; + } + if (!canCallFunction) { + throw Context.reportRuntimeError2( + "msg.no.function.interface.conversion", + String.valueOf(function), + cl.getClass().getName()); + } + adapter = new InterfaceAdapter(cf, cl); + cache.cacheInterfaceAdapter(cl, adapter); + } + return VMBridge.instance.newInterfaceProxy( + adapter.proxyHelper, cf, adapter, function, topScope); + } + + private InterfaceAdapter(ContextFactory cf, Class cl) + { + this.proxyHelper + = VMBridge.instance.getInterfaceProxyHelper( + cf, new Class[] { cl }); + } + + public Object invoke(ContextFactory cf, + final Object target, + final Scriptable topScope, + final Method method, + final Object[] args) + { + ContextAction action = new ContextAction() { + public Object run(Context cx) + { + return invokeImpl(cx, target, topScope, method, args); + } + }; + return cf.call(action); + } + + Object invokeImpl(Context cx, + Object target, + Scriptable topScope, + Method method, + Object[] args) + { + int N = (args == null) ? 0 : args.length; + + Callable function = (Callable)target; + Scriptable thisObj = topScope; + Object[] jsargs = new Object[N + 1]; + jsargs[N] = method.getName(); + if (N != 0) { + WrapFactory wf = cx.getWrapFactory(); + for (int i = 0; i != N; ++i) { + jsargs[i] = wf.wrap(cx, topScope, args[i], null); + } + } + + Object result = function.call(cx, topScope, thisObj, jsargs); + Class javaResultType = method.getReturnType(); + if (javaResultType == Void.TYPE) { + result = null; + } else { + result = Context.jsToJava(result, javaResultType); + } + return result; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpretedFunction.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpretedFunction.java new file mode 100644 index 0000000..db84299 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpretedFunction.java @@ -0,0 +1,221 @@ +/* -*- 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): + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * + * 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 org.mozilla.javascript.debug.DebuggableScript; + +final class InterpretedFunction extends NativeFunction implements Script +{ + static final long serialVersionUID = 541475680333911468L; + + InterpreterData idata; + SecurityController securityController; + Object securityDomain; + Scriptable[] functionRegExps; + + private InterpretedFunction(InterpreterData idata, + Object staticSecurityDomain) + { + this.idata = idata; + + // Always get Context from the current thread to + // avoid security breaches via passing mangled Context instances + // with bogus SecurityController + Context cx = Context.getContext(); + SecurityController sc = cx.getSecurityController(); + Object dynamicDomain; + if (sc != null) { + dynamicDomain = sc.getDynamicSecurityDomain(staticSecurityDomain); + } else { + if (staticSecurityDomain != null) { + throw new IllegalArgumentException(); + } + dynamicDomain = null; + } + + this.securityController = sc; + this.securityDomain = dynamicDomain; + } + + private InterpretedFunction(InterpretedFunction parent, int index) + { + this.idata = parent.idata.itsNestedFunctions[index]; + this.securityController = parent.securityController; + this.securityDomain = parent.securityDomain; + } + + /** + * Create script from compiled bytecode. + */ + static InterpretedFunction createScript(InterpreterData idata, + Object staticSecurityDomain) + { + InterpretedFunction f; + f = new InterpretedFunction(idata, staticSecurityDomain); + return f; + } + + /** + * Create function compiled from Function(...) constructor. + */ + static InterpretedFunction createFunction(Context cx,Scriptable scope, + InterpreterData idata, + Object staticSecurityDomain) + { + InterpretedFunction f; + f = new InterpretedFunction(idata, staticSecurityDomain); + f.initInterpretedFunction(cx, scope); + return f; + } + + /** + * Create function embedded in script or another function. + */ + static InterpretedFunction createFunction(Context cx, Scriptable scope, + InterpretedFunction parent, + int index) + { + InterpretedFunction f = new InterpretedFunction(parent, index); + f.initInterpretedFunction(cx, scope); + return f; + } + + Scriptable[] createRegExpWraps(Context cx, Scriptable scope) + { + if (idata.itsRegExpLiterals == null) Kit.codeBug(); + + RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx); + int N = idata.itsRegExpLiterals.length; + Scriptable[] array = new Scriptable[N]; + for (int i = 0; i != N; ++i) { + array[i] = rep.wrapRegExp(cx, scope, idata.itsRegExpLiterals[i]); + } + return array; + } + + private void initInterpretedFunction(Context cx, Scriptable scope) + { + initScriptFunction(cx, scope); + if (idata.itsRegExpLiterals != null) { + functionRegExps = createRegExpWraps(cx, scope); + } + } + + public String getFunctionName() + { + return (idata.itsName == null) ? "" : idata.itsName; + } + + /** + * Calls the function. + * @param cx the current context + * @param scope the scope used for the call + * @param thisObj the value of "this" + * @param args function arguments. Must not be null. You can use + * {@link ScriptRuntime#emptyArgs} to pass empty arguments. + * @return the result of the function call. + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + if (!ScriptRuntime.hasTopCall(cx)) { + return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args); + } + return Interpreter.interpret(this, cx, scope, thisObj, args); + } + + public Object exec(Context cx, Scriptable scope) + { + if (idata.itsFunctionType != 0) { + // Can only be applied to scripts + throw new IllegalStateException(); + } + if (!ScriptRuntime.hasTopCall(cx)) { + // It will go through "call" path. but they are equivalent + return ScriptRuntime.doTopCall( + this, cx, scope, scope, ScriptRuntime.emptyArgs); + } + return Interpreter.interpret( + this, cx, scope, scope, ScriptRuntime.emptyArgs); + } + + public String getEncodedSource() + { + return Interpreter.getEncodedSource(idata); + } + + public DebuggableScript getDebuggableView() + { + return idata; + } + + public Object resumeGenerator(Context cx, Scriptable scope, int operation, + Object state, Object value) + { + return Interpreter.resumeGenerator(cx, scope, operation, state, value); + } + + protected int getLanguageVersion() + { + return idata.languageVersion; + } + + protected int getParamCount() + { + return idata.argCount; + } + + protected int getParamAndVarCount() + { + return idata.argNames.length; + } + + protected String getParamOrVarName(int index) + { + return idata.argNames[index]; + } + + protected boolean getParamOrVarConst(int index) + { + return idata.argIsConst[index]; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Interpreter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Interpreter.java new file mode 100644 index 0000000..a68c025 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Interpreter.java @@ -0,0 +1,4643 @@ +/* -*- 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 + * Terry Lucas + * Roger Lawrence + * Milen Nankov + * Hannes Wallnoefer + * + * 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.io.PrintStream; +import java.io.Serializable; +import java.util.List; +import java.util.ArrayList; + +import org.mozilla.javascript.continuations.Continuation; +import org.mozilla.javascript.debug.DebugFrame; + +public class Interpreter implements Evaluator +{ + +// Additional interpreter-specific codes + + private static final int + + // Stack: ... value1 -> ... value1 value1 + Icode_DUP = -1, + + // Stack: ... value2 value1 -> ... value2 value1 value2 value1 + Icode_DUP2 = -2, + + // Stack: ... value2 value1 -> ... value1 value2 + Icode_SWAP = -3, + + // Stack: ... value1 -> ... + Icode_POP = -4, + + // Store stack top into return register and then pop it + Icode_POP_RESULT = -5, + + // To jump conditionally and pop additional stack value + Icode_IFEQ_POP = -6, + + // various types of ++/-- + Icode_VAR_INC_DEC = -7, + Icode_NAME_INC_DEC = -8, + Icode_PROP_INC_DEC = -9, + Icode_ELEM_INC_DEC = -10, + Icode_REF_INC_DEC = -11, + + // load/save scope from/to local + Icode_SCOPE_LOAD = -12, + Icode_SCOPE_SAVE = -13, + + Icode_TYPEOFNAME = -14, + + // helper for function calls + Icode_NAME_AND_THIS = -15, + Icode_PROP_AND_THIS = -16, + Icode_ELEM_AND_THIS = -17, + Icode_VALUE_AND_THIS = -18, + + // Create closure object for nested functions + Icode_CLOSURE_EXPR = -19, + Icode_CLOSURE_STMT = -20, + + // Special calls + Icode_CALLSPECIAL = -21, + + // To return undefined value + Icode_RETUNDEF = -22, + + // Exception handling implementation + Icode_GOSUB = -23, + Icode_STARTSUB = -24, + Icode_RETSUB = -25, + + // To indicating a line number change in icodes. + Icode_LINE = -26, + + // To store shorts and ints inline + Icode_SHORTNUMBER = -27, + Icode_INTNUMBER = -28, + + // To create and populate array to hold values for [] and {} literals + Icode_LITERAL_NEW = -29, + Icode_LITERAL_SET = -30, + + // Array literal with skipped index like [1,,2] + Icode_SPARE_ARRAYLIT = -31, + + // Load index register to prepare for the following index operation + Icode_REG_IND_C0 = -32, + Icode_REG_IND_C1 = -33, + Icode_REG_IND_C2 = -34, + Icode_REG_IND_C3 = -35, + Icode_REG_IND_C4 = -36, + Icode_REG_IND_C5 = -37, + Icode_REG_IND1 = -38, + Icode_REG_IND2 = -39, + Icode_REG_IND4 = -40, + + // Load string register to prepare for the following string operation + Icode_REG_STR_C0 = -41, + Icode_REG_STR_C1 = -42, + Icode_REG_STR_C2 = -43, + Icode_REG_STR_C3 = -44, + Icode_REG_STR1 = -45, + Icode_REG_STR2 = -46, + Icode_REG_STR4 = -47, + + // Version of getvar/setvar that read var index directly from bytecode + Icode_GETVAR1 = -48, + Icode_SETVAR1 = -49, + + // Load unefined + Icode_UNDEF = -50, + Icode_ZERO = -51, + Icode_ONE = -52, + + // entrance and exit from .() + Icode_ENTERDQ = -53, + Icode_LEAVEDQ = -54, + + Icode_TAIL_CALL = -55, + + // Clear local to allow GC its context + Icode_LOCAL_CLEAR = -56, + + // Literal get/set + Icode_LITERAL_GETTER = -57, + Icode_LITERAL_SETTER = -58, + + // const + Icode_SETCONST = -59, + Icode_SETCONSTVAR = -60, + Icode_SETCONSTVAR1 = -61, + + // Generator opcodes (along with Token.YIELD) + Icode_GENERATOR = -62, + Icode_GENERATOR_END = -63, + + Icode_DEBUGGER = -64, + + // Last icode + MIN_ICODE = -64; + + // data for parsing + + private CompilerEnvirons compilerEnv; + + private boolean itsInFunctionFlag; + private boolean itsInTryFlag; + + private InterpreterData itsData; + private ScriptOrFnNode scriptOrFn; + private int itsICodeTop; + private int itsStackDepth; + private int itsLineNumber; + private int itsDoubleTableTop; + private ObjToIntMap itsStrings = new ObjToIntMap(20); + private int itsLocalTop; + + private static final int MIN_LABEL_TABLE_SIZE = 32; + private static final int MIN_FIXUP_TABLE_SIZE = 40; + private int[] itsLabelTable; + private int itsLabelTableTop; +// itsFixupTable[i] = (label_index << 32) | fixup_site + private long[] itsFixupTable; + private int itsFixupTableTop; + private ObjArray itsLiteralIds = new ObjArray(); + + private int itsExceptionTableTop; + private static final int EXCEPTION_TRY_START_SLOT = 0; + private static final int EXCEPTION_TRY_END_SLOT = 1; + private static final int EXCEPTION_HANDLER_SLOT = 2; + private static final int EXCEPTION_TYPE_SLOT = 3; + private static final int EXCEPTION_LOCAL_SLOT = 4; + private static final int EXCEPTION_SCOPE_SLOT = 5; + // SLOT_SIZE: space for try start/end, handler, start, handler type, + // exception local and scope local + private static final int EXCEPTION_SLOT_SIZE = 6; + +// ECF_ or Expression Context Flags constants: for now only TAIL is available + private static final int ECF_TAIL = 1 << 0; + + /** + * Class to hold data corresponding to one interpreted call stack frame. + */ + private static class CallFrame implements Cloneable, Serializable + { + static final long serialVersionUID = -2843792508994958978L; + + CallFrame parentFrame; + // amount of stack frames before this one on the interpretation stack + int frameIndex; + // If true indicates read-only frame that is a part of continuation + boolean frozen; + + InterpretedFunction fnOrScript; + InterpreterData idata; + +// Stack structure +// stack[0 <= i < localShift]: arguments and local variables +// stack[localShift <= i <= emptyStackTop]: used for local temporaries +// stack[emptyStackTop < i < stack.length]: stack data +// sDbl[i]: if stack[i] is UniqueTag.DOUBLE_MARK, sDbl[i] holds the number value + + Object[] stack; + int[] stackAttributes; + double[] sDbl; + CallFrame varSource; // defaults to this unless continuation frame + int localShift; + int emptyStackTop; + + DebugFrame debuggerFrame; + boolean useActivation; + + Scriptable thisObj; + Scriptable[] scriptRegExps; + +// The values that change during interpretation + + Object result; + double resultDbl; + int pc; + int pcPrevBranch; + int pcSourceLineStart; + Scriptable scope; + + int savedStackTop; + int savedCallOp; + Object throwable; + + CallFrame cloneFrozen() + { + if (!frozen) Kit.codeBug(); + + CallFrame copy; + try { + copy = (CallFrame)clone(); + } catch (CloneNotSupportedException ex) { + throw new IllegalStateException(); + } + + // clone stack but keep varSource to point to values + // from this frame to share variables. + + copy.stack = stack.clone(); + copy.stackAttributes = stackAttributes.clone(); + copy.sDbl = sDbl.clone(); + + copy.frozen = false; + return copy; + } + } + + private static final class ContinuationJump implements Serializable + { + static final long serialVersionUID = 7687739156004308247L; + + CallFrame capturedFrame; + CallFrame branchFrame; + Object result; + double resultDbl; + + ContinuationJump(Continuation c, CallFrame current) + { + this.capturedFrame = (CallFrame)c.getImplementation(); + if (this.capturedFrame == null || current == null) { + // Continuation and current execution does not share + // any frames if there is nothing to capture or + // if there is no currently executed frames + this.branchFrame = null; + } else { + // Search for branch frame where parent frame chains starting + // from captured and current meet. + CallFrame chain1 = this.capturedFrame; + CallFrame chain2 = current; + + // First work parents of chain1 or chain2 until the same + // frame depth. + int diff = chain1.frameIndex - chain2.frameIndex; + if (diff != 0) { + if (diff < 0) { + // swap to make sure that + // chain1.frameIndex > chain2.frameIndex and diff > 0 + chain1 = current; + chain2 = this.capturedFrame; + diff = -diff; + } + do { + chain1 = chain1.parentFrame; + } while (--diff != 0); + if (chain1.frameIndex != chain2.frameIndex) Kit.codeBug(); + } + + // Now walk parents in parallel until a shared frame is found + // or until the root is reached. + while (chain1 != chain2 && chain1 != null) { + chain1 = chain1.parentFrame; + chain2 = chain2.parentFrame; + } + + this.branchFrame = chain1; + if (this.branchFrame != null && !this.branchFrame.frozen) + Kit.codeBug(); + } + } + } + + private static CallFrame captureFrameForGenerator(CallFrame frame) { + frame.frozen = true; + CallFrame result = frame.cloneFrozen(); + frame.frozen = false; + + // now isolate this frame from its previous context + result.parentFrame = null; + result.frameIndex = 0; + + return result; + } + + static { + // Checks for byte code consistencies, good compiler can eliminate them + + if (Token.LAST_BYTECODE_TOKEN > 127) { + String str = "Violation of Token.LAST_BYTECODE_TOKEN <= 127"; + System.err.println(str); + throw new IllegalStateException(str); + } + if (MIN_ICODE < -128) { + String str = "Violation of Interpreter.MIN_ICODE >= -128"; + System.err.println(str); + throw new IllegalStateException(str); + } + } + + private static String bytecodeName(int bytecode) + { + if (!validBytecode(bytecode)) { + throw new IllegalArgumentException(String.valueOf(bytecode)); + } + + if (!Token.printICode) { + return String.valueOf(bytecode); + } + + if (validTokenCode(bytecode)) { + return Token.name(bytecode); + } + + switch (bytecode) { + case Icode_DUP: return "DUP"; + case Icode_DUP2: return "DUP2"; + case Icode_SWAP: return "SWAP"; + case Icode_POP: return "POP"; + case Icode_POP_RESULT: return "POP_RESULT"; + case Icode_IFEQ_POP: return "IFEQ_POP"; + case Icode_VAR_INC_DEC: return "VAR_INC_DEC"; + case Icode_NAME_INC_DEC: return "NAME_INC_DEC"; + case Icode_PROP_INC_DEC: return "PROP_INC_DEC"; + case Icode_ELEM_INC_DEC: return "ELEM_INC_DEC"; + case Icode_REF_INC_DEC: return "REF_INC_DEC"; + case Icode_SCOPE_LOAD: return "SCOPE_LOAD"; + case Icode_SCOPE_SAVE: return "SCOPE_SAVE"; + case Icode_TYPEOFNAME: return "TYPEOFNAME"; + case Icode_NAME_AND_THIS: return "NAME_AND_THIS"; + case Icode_PROP_AND_THIS: return "PROP_AND_THIS"; + case Icode_ELEM_AND_THIS: return "ELEM_AND_THIS"; + case Icode_VALUE_AND_THIS: return "VALUE_AND_THIS"; + case Icode_CLOSURE_EXPR: return "CLOSURE_EXPR"; + case Icode_CLOSURE_STMT: return "CLOSURE_STMT"; + case Icode_CALLSPECIAL: return "CALLSPECIAL"; + case Icode_RETUNDEF: return "RETUNDEF"; + case Icode_GOSUB: return "GOSUB"; + case Icode_STARTSUB: return "STARTSUB"; + case Icode_RETSUB: return "RETSUB"; + case Icode_LINE: return "LINE"; + case Icode_SHORTNUMBER: return "SHORTNUMBER"; + case Icode_INTNUMBER: return "INTNUMBER"; + case Icode_LITERAL_NEW: return "LITERAL_NEW"; + case Icode_LITERAL_SET: return "LITERAL_SET"; + case Icode_SPARE_ARRAYLIT: return "SPARE_ARRAYLIT"; + case Icode_REG_IND_C0: return "REG_IND_C0"; + case Icode_REG_IND_C1: return "REG_IND_C1"; + case Icode_REG_IND_C2: return "REG_IND_C2"; + case Icode_REG_IND_C3: return "REG_IND_C3"; + case Icode_REG_IND_C4: return "REG_IND_C4"; + case Icode_REG_IND_C5: return "REG_IND_C5"; + case Icode_REG_IND1: return "LOAD_IND1"; + case Icode_REG_IND2: return "LOAD_IND2"; + case Icode_REG_IND4: return "LOAD_IND4"; + case Icode_REG_STR_C0: return "REG_STR_C0"; + case Icode_REG_STR_C1: return "REG_STR_C1"; + case Icode_REG_STR_C2: return "REG_STR_C2"; + case Icode_REG_STR_C3: return "REG_STR_C3"; + case Icode_REG_STR1: return "LOAD_STR1"; + case Icode_REG_STR2: return "LOAD_STR2"; + case Icode_REG_STR4: return "LOAD_STR4"; + case Icode_GETVAR1: return "GETVAR1"; + case Icode_SETVAR1: return "SETVAR1"; + case Icode_UNDEF: return "UNDEF"; + case Icode_ZERO: return "ZERO"; + case Icode_ONE: return "ONE"; + case Icode_ENTERDQ: return "ENTERDQ"; + case Icode_LEAVEDQ: return "LEAVEDQ"; + case Icode_TAIL_CALL: return "TAIL_CALL"; + case Icode_LOCAL_CLEAR: return "LOCAL_CLEAR"; + case Icode_LITERAL_GETTER: return "LITERAL_GETTER"; + case Icode_LITERAL_SETTER: return "LITERAL_SETTER"; + case Icode_SETCONST: return "SETCONST"; + case Icode_SETCONSTVAR: return "SETCONSTVAR"; + case Icode_SETCONSTVAR1: return "SETCONSTVAR1"; + case Icode_GENERATOR: return "GENERATOR"; + case Icode_GENERATOR_END: return "GENERATOR_END"; + case Icode_DEBUGGER: return "DEBUGGER"; + } + + // icode without name + throw new IllegalStateException(String.valueOf(bytecode)); + } + + private static boolean validIcode(int icode) + { + return MIN_ICODE <= icode && icode <= -1; + } + + private static boolean validTokenCode(int token) + { + return Token.FIRST_BYTECODE_TOKEN <= token + && token <= Token.LAST_BYTECODE_TOKEN; + } + + private static boolean validBytecode(int bytecode) + { + return validIcode(bytecode) || validTokenCode(bytecode); + } + + public Object compile(CompilerEnvirons compilerEnv, + ScriptOrFnNode tree, + String encodedSource, + boolean returnFunction) + { + this.compilerEnv = compilerEnv; + new NodeTransformer().transform(tree); + + if (Token.printTrees) { + /*APPJET*///System.out.println(tree.toStringTree(tree)); + } + + if (returnFunction) { + tree = tree.getFunctionNode(0); + } + + scriptOrFn = tree; + itsData = new InterpreterData(compilerEnv.getLanguageVersion(), + scriptOrFn.getSourceName(), + encodedSource); + itsData.topLevel = true; + + if (returnFunction) { + generateFunctionICode(); + } else { + generateICodeFromTree(scriptOrFn); + } + + return itsData; + } + + public Script createScriptObject(Object bytecode, Object staticSecurityDomain) + { + if(bytecode != itsData) + { + Kit.codeBug(); + } + return InterpretedFunction.createScript(itsData, + staticSecurityDomain); + } + + public void setEvalScriptFlag(Script script) { + ((InterpretedFunction)script).idata.evalScriptFlag = true; + } + + + public Function createFunctionObject(Context cx, Scriptable scope, + Object bytecode, Object staticSecurityDomain) + { + if(bytecode != itsData) + { + Kit.codeBug(); + } + return InterpretedFunction.createFunction(cx, scope, itsData, + staticSecurityDomain); + } + + private void generateFunctionICode() + { + itsInFunctionFlag = true; + + FunctionNode theFunction = (FunctionNode)scriptOrFn; + + itsData.itsFunctionType = theFunction.getFunctionType(); + itsData.itsNeedsActivation = theFunction.requiresActivation(); + itsData.itsName = theFunction.getFunctionName(); + if (!theFunction.getIgnoreDynamicScope()) { + if (compilerEnv.isUseDynamicScope()) { + itsData.useDynamicScope = true; + } + } + if (theFunction.isGenerator()) { + addIcode(Icode_GENERATOR); + addUint16(theFunction.getBaseLineno() & 0xFFFF); + } + + generateICodeFromTree(theFunction.getLastChild()); + } + + private void generateICodeFromTree(Node tree) + { + generateNestedFunctions(); + + generateRegExpLiterals(); + + visitStatement(tree, 0); + fixLabelGotos(); + // add RETURN_RESULT only to scripts as function always ends with RETURN + if (itsData.itsFunctionType == 0) { + addToken(Token.RETURN_RESULT); + } + + if (itsData.itsICode.length != itsICodeTop) { + // Make itsData.itsICode length exactly itsICodeTop to save memory + // and catch bugs with jumps beyond icode as early as possible + byte[] tmp = new byte[itsICodeTop]; + System.arraycopy(itsData.itsICode, 0, tmp, 0, itsICodeTop); + itsData.itsICode = tmp; + } + if (itsStrings.size() == 0) { + itsData.itsStringTable = null; + } else { + itsData.itsStringTable = new String[itsStrings.size()]; + ObjToIntMap.Iterator iter = itsStrings.newIterator(); + for (iter.start(); !iter.done(); iter.next()) { + String str = (String)iter.getKey(); + int index = iter.getValue(); + if (itsData.itsStringTable[index] != null) Kit.codeBug(); + itsData.itsStringTable[index] = str; + } + } + if (itsDoubleTableTop == 0) { + itsData.itsDoubleTable = null; + } else if (itsData.itsDoubleTable.length != itsDoubleTableTop) { + double[] tmp = new double[itsDoubleTableTop]; + System.arraycopy(itsData.itsDoubleTable, 0, tmp, 0, + itsDoubleTableTop); + itsData.itsDoubleTable = tmp; + } + if (itsExceptionTableTop != 0 + && itsData.itsExceptionTable.length != itsExceptionTableTop) + { + int[] tmp = new int[itsExceptionTableTop]; + System.arraycopy(itsData.itsExceptionTable, 0, tmp, 0, + itsExceptionTableTop); + itsData.itsExceptionTable = tmp; + } + + itsData.itsMaxVars = scriptOrFn.getParamAndVarCount(); + // itsMaxFrameArray: interpret method needs this amount for its + // stack and sDbl arrays + itsData.itsMaxFrameArray = itsData.itsMaxVars + + itsData.itsMaxLocals + + itsData.itsMaxStack; + + itsData.argNames = scriptOrFn.getParamAndVarNames(); + itsData.argIsConst = scriptOrFn.getParamAndVarConst(); + itsData.argCount = scriptOrFn.getParamCount(); + + itsData.encodedSourceStart = scriptOrFn.getEncodedSourceStart(); + itsData.encodedSourceEnd = scriptOrFn.getEncodedSourceEnd(); + + if (itsLiteralIds.size() != 0) { + itsData.literalIds = itsLiteralIds.toArray(); + } + + if (Token.printICode) dumpICode(itsData); + } + + private void generateNestedFunctions() + { + int functionCount = scriptOrFn.getFunctionCount(); + if (functionCount == 0) return; + + InterpreterData[] array = new InterpreterData[functionCount]; + for (int i = 0; i != functionCount; i++) { + FunctionNode def = scriptOrFn.getFunctionNode(i); + Interpreter jsi = new Interpreter(); + jsi.compilerEnv = compilerEnv; + jsi.scriptOrFn = def; + jsi.itsData = new InterpreterData(itsData); + jsi.generateFunctionICode(); + array[i] = jsi.itsData; + } + itsData.itsNestedFunctions = array; + } + + private void generateRegExpLiterals() + { + int N = scriptOrFn.getRegexpCount(); + if (N == 0) return; + + Context cx = Context.getContext(); + RegExpProxy rep = ScriptRuntime.checkRegExpProxy(cx); + Object[] array = new Object[N]; + for (int i = 0; i != N; i++) { + String string = scriptOrFn.getRegexpString(i); + String flags = scriptOrFn.getRegexpFlags(i); + array[i] = rep.compileRegExp(cx, string, flags); + } + itsData.itsRegExpLiterals = array; + } + + private void updateLineNumber(Node node) + { + int lineno = node.getLineno(); + if (lineno != itsLineNumber && lineno >= 0) { + if (itsData.firstLinePC < 0) { + itsData.firstLinePC = lineno; + } + itsLineNumber = lineno; + addIcode(Icode_LINE); + addUint16(lineno & 0xFFFF); + } + } + + private RuntimeException badTree(Node node) + { + throw new RuntimeException(node.toString()); + } + + private void visitStatement(Node node, int initialStackDepth) + { + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + + case Token.FUNCTION: + { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + int fnType = scriptOrFn.getFunctionNode(fnIndex). + getFunctionType(); + // Only function expressions or function expression + // statements need closure code creating new function + // object on stack as function statements are initialized + // at script/function start. + // In addition, function expressions can not be present here + // at statement level, they must only be present as expressions. + if (fnType == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) { + addIndexOp(Icode_CLOSURE_STMT, fnIndex); + } else { + if (fnType != FunctionNode.FUNCTION_STATEMENT) { + throw Kit.codeBug(); + } + } + // For function statements or function expression statements + // in scripts, we need to ensure that the result of the script + // is the function if it is the last statement in the script. + // For example, eval("function () {}") should return a + // function, not undefined. + if (!itsInFunctionFlag) { + addIndexOp(Icode_CLOSURE_EXPR, fnIndex); + stackChange(1); + addIcode(Icode_POP_RESULT); + stackChange(-1); + } + } + break; + + case Token.LABEL: + case Token.LOOP: + case Token.BLOCK: + case Token.EMPTY: + case Token.WITH: + updateLineNumber(node); + case Token.SCRIPT: + // fall through + while (child != null) { + visitStatement(child, initialStackDepth); + child = child.getNext(); + } + break; + + case Token.ENTERWITH: + visitExpression(child, 0); + addToken(Token.ENTERWITH); + stackChange(-1); + break; + + case Token.LEAVEWITH: + addToken(Token.LEAVEWITH); + break; + + case Token.LOCAL_BLOCK: + { + int local = allocLocal(); + node.putIntProp(Node.LOCAL_PROP, local); + updateLineNumber(node); + while (child != null) { + visitStatement(child, initialStackDepth); + child = child.getNext(); + } + addIndexOp(Icode_LOCAL_CLEAR, local); + releaseLocal(local); + } + break; + + case Token.DEBUGGER: + addIcode(Icode_DEBUGGER); + break; + + case Token.SWITCH: + updateLineNumber(node); + // See comments in IRFactory.createSwitch() for description + // of SWITCH node + { + visitExpression(child, 0); + for (Node.Jump caseNode = (Node.Jump)child.getNext(); + caseNode != null; + caseNode = (Node.Jump)caseNode.getNext()) + { + if (caseNode.getType() != Token.CASE) + throw badTree(caseNode); + Node test = caseNode.getFirstChild(); + addIcode(Icode_DUP); + stackChange(1); + visitExpression(test, 0); + addToken(Token.SHEQ); + stackChange(-1); + // If true, Icode_IFEQ_POP will jump and remove case + // value from stack + addGoto(caseNode.target, Icode_IFEQ_POP); + stackChange(-1); + } + addIcode(Icode_POP); + stackChange(-1); + } + break; + + case Token.TARGET: + markTargetLabel(node); + break; + + case Token.IFEQ : + case Token.IFNE : + { + Node target = ((Node.Jump)node).target; + visitExpression(child, 0); + addGoto(target, type); + stackChange(-1); + } + break; + + case Token.GOTO: + { + Node target = ((Node.Jump)node).target; + addGoto(target, type); + } + break; + + case Token.JSR: + { + Node target = ((Node.Jump)node).target; + addGoto(target, Icode_GOSUB); + } + break; + + case Token.FINALLY: + { + // Account for incomming GOTOSUB address + stackChange(1); + int finallyRegister = getLocalBlockRef(node); + addIndexOp(Icode_STARTSUB, finallyRegister); + stackChange(-1); + while (child != null) { + visitStatement(child, initialStackDepth); + child = child.getNext(); + } + addIndexOp(Icode_RETSUB, finallyRegister); + } + break; + + case Token.EXPR_VOID: + case Token.EXPR_RESULT: + updateLineNumber(node); + visitExpression(child, 0); + addIcode((type == Token.EXPR_VOID) ? Icode_POP : Icode_POP_RESULT); + stackChange(-1); + break; + + case Token.TRY: + { + Node.Jump tryNode = (Node.Jump)node; + int exceptionObjectLocal = getLocalBlockRef(tryNode); + int scopeLocal = allocLocal(); + + addIndexOp(Icode_SCOPE_SAVE, scopeLocal); + + int tryStart = itsICodeTop; + boolean savedFlag = itsInTryFlag; + itsInTryFlag = true; + while (child != null) { + visitStatement(child, initialStackDepth); + child = child.getNext(); + } + itsInTryFlag = savedFlag; + + Node catchTarget = tryNode.target; + if (catchTarget != null) { + int catchStartPC + = itsLabelTable[getTargetLabel(catchTarget)]; + addExceptionHandler( + tryStart, catchStartPC, catchStartPC, + false, exceptionObjectLocal, scopeLocal); + } + Node finallyTarget = tryNode.getFinally(); + if (finallyTarget != null) { + int finallyStartPC + = itsLabelTable[getTargetLabel(finallyTarget)]; + addExceptionHandler( + tryStart, finallyStartPC, finallyStartPC, + true, exceptionObjectLocal, scopeLocal); + } + + addIndexOp(Icode_LOCAL_CLEAR, scopeLocal); + releaseLocal(scopeLocal); + } + break; + + case Token.CATCH_SCOPE: + { + int localIndex = getLocalBlockRef(node); + int scopeIndex = node.getExistingIntProp(Node.CATCH_SCOPE_PROP); + String name = child.getString(); + child = child.getNext(); + visitExpression(child, 0); // load expression object + addStringPrefix(name); + addIndexPrefix(localIndex); + addToken(Token.CATCH_SCOPE); + addUint8(scopeIndex != 0 ? 1 : 0); + stackChange(-1); + } + break; + + case Token.THROW: + updateLineNumber(node); + visitExpression(child, 0); + addToken(Token.THROW); + addUint16(itsLineNumber & 0xFFFF); + stackChange(-1); + break; + + case Token.RETHROW: + updateLineNumber(node); + addIndexOp(Token.RETHROW, getLocalBlockRef(node)); + break; + + case Token.RETURN: + updateLineNumber(node); + if (node.getIntProp(Node.GENERATOR_END_PROP, 0) != 0) { + // We're in a generator, so change RETURN to GENERATOR_END + addIcode(Icode_GENERATOR_END); + addUint16(itsLineNumber & 0xFFFF); + } else if (child != null) { + visitExpression(child, ECF_TAIL); + addToken(Token.RETURN); + stackChange(-1); + } else { + addIcode(Icode_RETUNDEF); + } + break; + + case Token.RETURN_RESULT: + updateLineNumber(node); + addToken(Token.RETURN_RESULT); + break; + + case Token.ENUM_INIT_KEYS: + case Token.ENUM_INIT_VALUES: + case Token.ENUM_INIT_ARRAY: + visitExpression(child, 0); + addIndexOp(type, getLocalBlockRef(node)); + stackChange(-1); + break; + + case Icode_GENERATOR: + break; + + default: + throw badTree(node); + } + + if (itsStackDepth != initialStackDepth) { + throw Kit.codeBug(); + } + } + + private void visitExpression(Node node, int contextFlags) + { + int type = node.getType(); + Node child = node.getFirstChild(); + int savedStackDepth = itsStackDepth; + switch (type) { + + case Token.FUNCTION: + { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + FunctionNode fn = scriptOrFn.getFunctionNode(fnIndex); + // See comments in visitStatement for Token.FUNCTION case + if (fn.getFunctionType() != FunctionNode.FUNCTION_EXPRESSION) { + throw Kit.codeBug(); + } + addIndexOp(Icode_CLOSURE_EXPR, fnIndex); + stackChange(1); + } + break; + + case Token.LOCAL_LOAD: + { + int localIndex = getLocalBlockRef(node); + addIndexOp(Token.LOCAL_LOAD, localIndex); + stackChange(1); + } + break; + + case Token.COMMA: + { + Node lastChild = node.getLastChild(); + while (child != lastChild) { + visitExpression(child, 0); + addIcode(Icode_POP); + stackChange(-1); + child = child.getNext(); + } + // Preserve tail context flag if any + visitExpression(child, contextFlags & ECF_TAIL); + } + break; + + case Token.USE_STACK: + // Indicates that stack was modified externally, + // like placed catch object + stackChange(1); + break; + + case Token.REF_CALL: + case Token.CALL: + case Token.NEW: + { + if (type == Token.NEW) { + visitExpression(child, 0); + } else { + generateCallFunAndThis(child); + } + int argCount = 0; + while ((child = child.getNext()) != null) { + visitExpression(child, 0); + ++argCount; + } + int callType = node.getIntProp(Node.SPECIALCALL_PROP, + Node.NON_SPECIALCALL); + if (callType != Node.NON_SPECIALCALL) { + // embed line number and source filename + addIndexOp(Icode_CALLSPECIAL, argCount); + addUint8(callType); + addUint8(type == Token.NEW ? 1 : 0); + addUint16(itsLineNumber & 0xFFFF); + } else { + // Only use the tail call optimization if we're not in a try + // or we're not generating debug info (since the + // optimization will confuse the debugger) + if (type == Token.CALL && (contextFlags & ECF_TAIL) != 0 && + !compilerEnv.isGenerateDebugInfo() && !itsInTryFlag) + { + type = Icode_TAIL_CALL; + } + addIndexOp(type, argCount); + } + // adjust stack + if (type == Token.NEW) { + // new: f, args -> result + stackChange(-argCount); + } else { + // call: f, thisObj, args -> result + // ref_call: f, thisObj, args -> ref + stackChange(-1 - argCount); + } + if (argCount > itsData.itsMaxCalleeArgs) { + itsData.itsMaxCalleeArgs = argCount; + } + } + break; + + case Token.AND: + case Token.OR: + { + visitExpression(child, 0); + addIcode(Icode_DUP); + stackChange(1); + int afterSecondJumpStart = itsICodeTop; + int jump = (type == Token.AND) ? Token.IFNE : Token.IFEQ; + addGotoOp(jump); + stackChange(-1); + addIcode(Icode_POP); + stackChange(-1); + child = child.getNext(); + // Preserve tail context flag if any + visitExpression(child, contextFlags & ECF_TAIL); + resolveForwardGoto(afterSecondJumpStart); + } + break; + + case Token.HOOK: + { + Node ifThen = child.getNext(); + Node ifElse = ifThen.getNext(); + visitExpression(child, 0); + int elseJumpStart = itsICodeTop; + addGotoOp(Token.IFNE); + stackChange(-1); + // Preserve tail context flag if any + visitExpression(ifThen, contextFlags & ECF_TAIL); + int afterElseJumpStart = itsICodeTop; + addGotoOp(Token.GOTO); + resolveForwardGoto(elseJumpStart); + itsStackDepth = savedStackDepth; + // Preserve tail context flag if any + visitExpression(ifElse, contextFlags & ECF_TAIL); + resolveForwardGoto(afterElseJumpStart); + } + break; + + case Token.GETPROP: + case Token.GETPROPNOWARN: + visitExpression(child, 0); + child = child.getNext(); + addStringOp(type, child.getString()); + break; + + case Token.GETELEM: + case Token.DELPROP: + case Token.BITAND: + case Token.BITOR: + case Token.BITXOR: + case Token.LSH: + case Token.RSH: + case Token.URSH: + case Token.ADD: + case Token.SUB: + case Token.MOD: + case Token.DIV: + case Token.MUL: + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: + case Token.IN: + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: + visitExpression(child, 0); + child = child.getNext(); + visitExpression(child, 0); + addToken(type); + stackChange(-1); + break; + + case Token.POS: + case Token.NEG: + case Token.NOT: + case Token.BITNOT: + case Token.TYPEOF: + case Token.VOID: + visitExpression(child, 0); + if (type == Token.VOID) { + addIcode(Icode_POP); + addIcode(Icode_UNDEF); + } else { + addToken(type); + } + break; + + case Token.GET_REF: + case Token.DEL_REF: + visitExpression(child, 0); + addToken(type); + break; + + case Token.SETPROP: + case Token.SETPROP_OP: + { + visitExpression(child, 0); + child = child.getNext(); + String property = child.getString(); + child = child.getNext(); + if (type == Token.SETPROP_OP) { + addIcode(Icode_DUP); + stackChange(1); + addStringOp(Token.GETPROP, property); + // Compensate for the following USE_STACK + stackChange(-1); + } + visitExpression(child, 0); + addStringOp(Token.SETPROP, property); + stackChange(-1); + } + break; + + case Token.SETELEM: + case Token.SETELEM_OP: + visitExpression(child, 0); + child = child.getNext(); + visitExpression(child, 0); + child = child.getNext(); + if (type == Token.SETELEM_OP) { + addIcode(Icode_DUP2); + stackChange(2); + addToken(Token.GETELEM); + stackChange(-1); + // Compensate for the following USE_STACK + stackChange(-1); + } + visitExpression(child, 0); + addToken(Token.SETELEM); + stackChange(-2); + break; + + case Token.SET_REF: + case Token.SET_REF_OP: + visitExpression(child, 0); + child = child.getNext(); + if (type == Token.SET_REF_OP) { + addIcode(Icode_DUP); + stackChange(1); + addToken(Token.GET_REF); + // Compensate for the following USE_STACK + stackChange(-1); + } + visitExpression(child, 0); + addToken(Token.SET_REF); + stackChange(-1); + break; + + case Token.SETNAME: + { + String name = child.getString(); + visitExpression(child, 0); + child = child.getNext(); + visitExpression(child, 0); + addStringOp(Token.SETNAME, name); + stackChange(-1); + } + break; + + case Token.SETCONST: + { + String name = child.getString(); + visitExpression(child, 0); + child = child.getNext(); + visitExpression(child, 0); + addStringOp(Icode_SETCONST, name); + stackChange(-1); + } + break; + + case Token.TYPEOFNAME: + { + int index = -1; + // use typeofname if an activation frame exists + // since the vars all exist there instead of in jregs + if (itsInFunctionFlag && !itsData.itsNeedsActivation) + index = scriptOrFn.getIndexForNameNode(node); + if (index == -1) { + addStringOp(Icode_TYPEOFNAME, node.getString()); + stackChange(1); + } else { + addVarOp(Token.GETVAR, index); + stackChange(1); + addToken(Token.TYPEOF); + } + } + break; + + case Token.BINDNAME: + case Token.NAME: + case Token.STRING: + addStringOp(type, node.getString()); + stackChange(1); + break; + + case Token.INC: + case Token.DEC: + visitIncDec(node, child); + break; + + case Token.NUMBER: + { + double num = node.getDouble(); + int inum = (int)num; + if (inum == num) { + if (inum == 0) { + addIcode(Icode_ZERO); + // Check for negative zero + if (1.0 / num < 0.0) { + addToken(Token.NEG); + } + } else if (inum == 1) { + addIcode(Icode_ONE); + } else if ((short)inum == inum) { + addIcode(Icode_SHORTNUMBER); + // write short as uin16 bit pattern + addUint16(inum & 0xFFFF); + } else { + addIcode(Icode_INTNUMBER); + addInt(inum); + } + } else { + int index = getDoubleIndex(num); + addIndexOp(Token.NUMBER, index); + } + stackChange(1); + } + break; + + case Token.GETVAR: + { + if (itsData.itsNeedsActivation) Kit.codeBug(); + int index = scriptOrFn.getIndexForNameNode(node); + addVarOp(Token.GETVAR, index); + stackChange(1); + } + break; + + case Token.SETVAR: + { + if (itsData.itsNeedsActivation) Kit.codeBug(); + int index = scriptOrFn.getIndexForNameNode(child); + child = child.getNext(); + visitExpression(child, 0); + addVarOp(Token.SETVAR, index); + } + break; + + case Token.SETCONSTVAR: + { + if (itsData.itsNeedsActivation) Kit.codeBug(); + int index = scriptOrFn.getIndexForNameNode(child); + child = child.getNext(); + visitExpression(child, 0); + addVarOp(Token.SETCONSTVAR, index); + } + break; + + case Token.NULL: + case Token.THIS: + case Token.THISFN: + case Token.FALSE: + case Token.TRUE: + addToken(type); + stackChange(1); + break; + + case Token.ENUM_NEXT: + case Token.ENUM_ID: + addIndexOp(type, getLocalBlockRef(node)); + stackChange(1); + break; + + case Token.REGEXP: + { + int index = node.getExistingIntProp(Node.REGEXP_PROP); + addIndexOp(Token.REGEXP, index); + stackChange(1); + } + break; + + case Token.ARRAYLIT: + case Token.OBJECTLIT: + visitLiteral(node, child); + break; + + case Token.ARRAYCOMP: + visitArrayComprehension(node, child, child.getNext()); + break; + + case Token.REF_SPECIAL: + visitExpression(child, 0); + addStringOp(type, (String)node.getProp(Node.NAME_PROP)); + break; + + case Token.REF_MEMBER: + case Token.REF_NS_MEMBER: + case Token.REF_NAME: + case Token.REF_NS_NAME: + { + int memberTypeFlags = node.getIntProp(Node.MEMBER_TYPE_PROP, 0); + // generate possible target, possible namespace and member + int childCount = 0; + do { + visitExpression(child, 0); + ++childCount; + child = child.getNext(); + } while (child != null); + addIndexOp(type, memberTypeFlags); + stackChange(1 - childCount); + } + break; + + case Token.DOTQUERY: + { + int queryPC; + updateLineNumber(node); + visitExpression(child, 0); + addIcode(Icode_ENTERDQ); + stackChange(-1); + queryPC = itsICodeTop; + visitExpression(child.getNext(), 0); + addBackwardGoto(Icode_LEAVEDQ, queryPC); + } + break; + + case Token.DEFAULTNAMESPACE : + case Token.ESCXMLATTR : + case Token.ESCXMLTEXT : + visitExpression(child, 0); + addToken(type); + break; + + case Token.YIELD: + if (child != null) { + visitExpression(child, 0); + } else { + addIcode(Icode_UNDEF); + stackChange(1); + } + addToken(Token.YIELD); + addUint16(node.getLineno() & 0xFFFF); + break; + + case Token.WITHEXPR: { + Node enterWith = node.getFirstChild(); + Node with = enterWith.getNext(); + visitExpression(enterWith.getFirstChild(), 0); + addToken(Token.ENTERWITH); + stackChange(-1); + visitExpression(with.getFirstChild(), 0); + addToken(Token.LEAVEWITH); + break; + } + + default: + throw badTree(node); + } + if (savedStackDepth + 1 != itsStackDepth) { + Kit.codeBug(); + } + } + + private void generateCallFunAndThis(Node left) + { + // Generate code to place on stack function and thisObj + int type = left.getType(); + switch (type) { + case Token.NAME: { + String name = left.getString(); + // stack: ... -> ... function thisObj + addStringOp(Icode_NAME_AND_THIS, name); + stackChange(2); + break; + } + case Token.GETPROP: + case Token.GETELEM: { + Node target = left.getFirstChild(); + visitExpression(target, 0); + Node id = target.getNext(); + if (type == Token.GETPROP) { + String property = id.getString(); + // stack: ... target -> ... function thisObj + addStringOp(Icode_PROP_AND_THIS, property); + stackChange(1); + } else { + visitExpression(id, 0); + // stack: ... target id -> ... function thisObj + addIcode(Icode_ELEM_AND_THIS); + } + break; + } + default: + // Including Token.GETVAR + visitExpression(left, 0); + // stack: ... value -> ... function thisObj + addIcode(Icode_VALUE_AND_THIS); + stackChange(1); + break; + } + } + + private void visitIncDec(Node node, Node child) + { + int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP); + int childType = child.getType(); + switch (childType) { + case Token.GETVAR : { + if (itsData.itsNeedsActivation) Kit.codeBug(); + int i = scriptOrFn.getIndexForNameNode(child); + addVarOp(Icode_VAR_INC_DEC, i); + addUint8(incrDecrMask); + stackChange(1); + break; + } + case Token.NAME : { + String name = child.getString(); + addStringOp(Icode_NAME_INC_DEC, name); + addUint8(incrDecrMask); + stackChange(1); + break; + } + case Token.GETPROP : { + Node object = child.getFirstChild(); + visitExpression(object, 0); + String property = object.getNext().getString(); + addStringOp(Icode_PROP_INC_DEC, property); + addUint8(incrDecrMask); + break; + } + case Token.GETELEM : { + Node object = child.getFirstChild(); + visitExpression(object, 0); + Node index = object.getNext(); + visitExpression(index, 0); + addIcode(Icode_ELEM_INC_DEC); + addUint8(incrDecrMask); + stackChange(-1); + break; + } + case Token.GET_REF : { + Node ref = child.getFirstChild(); + visitExpression(ref, 0); + addIcode(Icode_REF_INC_DEC); + addUint8(incrDecrMask); + break; + } + default : { + throw badTree(node); + } + } + } + + private void visitLiteral(Node node, Node child) + { + int type = node.getType(); + int count; + Object[] propertyIds = null; + if (type == Token.ARRAYLIT) { + count = 0; + for (Node n = child; n != null; n = n.getNext()) { + ++count; + } + } else if (type == Token.OBJECTLIT) { + propertyIds = (Object[])node.getProp(Node.OBJECT_IDS_PROP); + count = propertyIds.length; + } else { + throw badTree(node); + } + addIndexOp(Icode_LITERAL_NEW, count); + stackChange(2); + while (child != null) { + int childType = child.getType(); + if (childType == Token.GET) { + visitExpression(child.getFirstChild(), 0); + addIcode(Icode_LITERAL_GETTER); + } else if (childType == Token.SET) { + visitExpression(child.getFirstChild(), 0); + addIcode(Icode_LITERAL_SETTER); + } else { + visitExpression(child, 0); + addIcode(Icode_LITERAL_SET); + } + stackChange(-1); + child = child.getNext(); + } + if (type == Token.ARRAYLIT) { + int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP); + if (skipIndexes == null) { + addToken(Token.ARRAYLIT); + } else { + int index = itsLiteralIds.size(); + itsLiteralIds.add(skipIndexes); + addIndexOp(Icode_SPARE_ARRAYLIT, index); + } + } else { + int index = itsLiteralIds.size(); + itsLiteralIds.add(propertyIds); + addIndexOp(Token.OBJECTLIT, index); + } + stackChange(-1); + } + + private void visitArrayComprehension(Node node, Node initStmt, Node expr) + { + // A bit of a hack: array comprehensions are implemented using + // statement nodes for the iteration, yet they appear in an + // expression context. So we pass the current stack depth to + // visitStatement so it can check that the depth is not altered + // by statements. + visitStatement(initStmt, itsStackDepth); + visitExpression(expr, 0); + } + + private int getLocalBlockRef(Node node) + { + Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP); + return localBlock.getExistingIntProp(Node.LOCAL_PROP); + } + + private int getTargetLabel(Node target) + { + int label = target.labelId(); + if (label != -1) { + return label; + } + label = itsLabelTableTop; + if (itsLabelTable == null || label == itsLabelTable.length) { + if (itsLabelTable == null) { + itsLabelTable = new int[MIN_LABEL_TABLE_SIZE]; + }else { + int[] tmp = new int[itsLabelTable.length * 2]; + System.arraycopy(itsLabelTable, 0, tmp, 0, label); + itsLabelTable = tmp; + } + } + itsLabelTableTop = label + 1; + itsLabelTable[label] = -1; + + target.labelId(label); + return label; + } + + private void markTargetLabel(Node target) + { + int label = getTargetLabel(target); + if (itsLabelTable[label] != -1) { + // Can mark label only once + Kit.codeBug(); + } + itsLabelTable[label] = itsICodeTop; + } + + private void addGoto(Node target, int gotoOp) + { + int label = getTargetLabel(target); + if (!(label < itsLabelTableTop)) Kit.codeBug(); + int targetPC = itsLabelTable[label]; + + if (targetPC != -1) { + addBackwardGoto(gotoOp, targetPC); + } else { + int gotoPC = itsICodeTop; + addGotoOp(gotoOp); + int top = itsFixupTableTop; + if (itsFixupTable == null || top == itsFixupTable.length) { + if (itsFixupTable == null) { + itsFixupTable = new long[MIN_FIXUP_TABLE_SIZE]; + } else { + long[] tmp = new long[itsFixupTable.length * 2]; + System.arraycopy(itsFixupTable, 0, tmp, 0, top); + itsFixupTable = tmp; + } + } + itsFixupTableTop = top + 1; + itsFixupTable[top] = ((long)label << 32) | gotoPC; + } + } + + private void fixLabelGotos() + { + for (int i = 0; i < itsFixupTableTop; i++) { + long fixup = itsFixupTable[i]; + int label = (int)(fixup >> 32); + int jumpSource = (int)fixup; + int pc = itsLabelTable[label]; + if (pc == -1) { + // Unlocated label + throw Kit.codeBug(); + } + resolveGoto(jumpSource, pc); + } + itsFixupTableTop = 0; + } + + private void addBackwardGoto(int gotoOp, int jumpPC) + { + int fromPC = itsICodeTop; + // Ensure that this is a jump backward + if (fromPC <= jumpPC) throw Kit.codeBug(); + addGotoOp(gotoOp); + resolveGoto(fromPC, jumpPC); + } + + private void resolveForwardGoto(int fromPC) + { + // Ensure that forward jump skips at least self bytecode + if (itsICodeTop < fromPC + 3) throw Kit.codeBug(); + resolveGoto(fromPC, itsICodeTop); + } + + private void resolveGoto(int fromPC, int jumpPC) + { + int offset = jumpPC - fromPC; + // Ensure that jumps do not overlap + if (0 <= offset && offset <= 2) throw Kit.codeBug(); + int offsetSite = fromPC + 1; + if (offset != (short)offset) { + if (itsData.longJumps == null) { + itsData.longJumps = new UintMap(); + } + itsData.longJumps.put(offsetSite, jumpPC); + offset = 0; + } + byte[] array = itsData.itsICode; + array[offsetSite] = (byte)(offset >> 8); + array[offsetSite + 1] = (byte)offset; + } + + private void addToken(int token) + { + if (!validTokenCode(token)) throw Kit.codeBug(); + addUint8(token); + } + + private void addIcode(int icode) + { + if (!validIcode(icode)) throw Kit.codeBug(); + // Write negative icode as uint8 bits + addUint8(icode & 0xFF); + } + + private void addUint8(int value) + { + if ((value & ~0xFF) != 0) throw Kit.codeBug(); + byte[] array = itsData.itsICode; + int top = itsICodeTop; + if (top == array.length) { + array = increaseICodeCapacity(1); + } + array[top] = (byte)value; + itsICodeTop = top + 1; + } + + private void addUint16(int value) + { + if ((value & ~0xFFFF) != 0) throw Kit.codeBug(); + byte[] array = itsData.itsICode; + int top = itsICodeTop; + if (top + 2 > array.length) { + array = increaseICodeCapacity(2); + } + array[top] = (byte)(value >>> 8); + array[top + 1] = (byte)value; + itsICodeTop = top + 2; + } + + private void addInt(int i) + { + byte[] array = itsData.itsICode; + int top = itsICodeTop; + if (top + 4 > array.length) { + array = increaseICodeCapacity(4); + } + array[top] = (byte)(i >>> 24); + array[top + 1] = (byte)(i >>> 16); + array[top + 2] = (byte)(i >>> 8); + array[top + 3] = (byte)i; + itsICodeTop = top + 4; + } + + private int getDoubleIndex(double num) + { + int index = itsDoubleTableTop; + if (index == 0) { + itsData.itsDoubleTable = new double[64]; + } else if (itsData.itsDoubleTable.length == index) { + double[] na = new double[index * 2]; + System.arraycopy(itsData.itsDoubleTable, 0, na, 0, index); + itsData.itsDoubleTable = na; + } + itsData.itsDoubleTable[index] = num; + itsDoubleTableTop = index + 1; + return index; + } + + private void addGotoOp(int gotoOp) + { + byte[] array = itsData.itsICode; + int top = itsICodeTop; + if (top + 3 > array.length) { + array = increaseICodeCapacity(3); + } + array[top] = (byte)gotoOp; + // Offset would written later + itsICodeTop = top + 1 + 2; + } + + private void addVarOp(int op, int varIndex) + { + switch (op) { + case Token.SETCONSTVAR: + if (varIndex < 128) { + addIcode(Icode_SETCONSTVAR1); + addUint8(varIndex); + return; + } + addIndexOp(Icode_SETCONSTVAR, varIndex); + return; + case Token.GETVAR: + case Token.SETVAR: + if (varIndex < 128) { + addIcode(op == Token.GETVAR ? Icode_GETVAR1 : Icode_SETVAR1); + addUint8(varIndex); + return; + } + // fallthrough + case Icode_VAR_INC_DEC: + addIndexOp(op, varIndex); + return; + } + throw Kit.codeBug(); + } + + private void addStringOp(int op, String str) + { + addStringPrefix(str); + if (validIcode(op)) { + addIcode(op); + } else { + addToken(op); + } + } + + private void addIndexOp(int op, int index) + { + addIndexPrefix(index); + if (validIcode(op)) { + addIcode(op); + } else { + addToken(op); + } + } + + private void addStringPrefix(String str) + { + int index = itsStrings.get(str, -1); + if (index == -1) { + index = itsStrings.size(); + itsStrings.put(str, index); + } + if (index < 4) { + addIcode(Icode_REG_STR_C0 - index); + } else if (index <= 0xFF) { + addIcode(Icode_REG_STR1); + addUint8(index); + } else if (index <= 0xFFFF) { + addIcode(Icode_REG_STR2); + addUint16(index); + } else { + addIcode(Icode_REG_STR4); + addInt(index); + } + } + + private void addIndexPrefix(int index) + { + if (index < 0) Kit.codeBug(); + if (index < 6) { + addIcode(Icode_REG_IND_C0 - index); + } else if (index <= 0xFF) { + addIcode(Icode_REG_IND1); + addUint8(index); + } else if (index <= 0xFFFF) { + addIcode(Icode_REG_IND2); + addUint16(index); + } else { + addIcode(Icode_REG_IND4); + addInt(index); + } + } + + private void addExceptionHandler(int icodeStart, int icodeEnd, + int handlerStart, boolean isFinally, + int exceptionObjectLocal, int scopeLocal) + { + int top = itsExceptionTableTop; + int[] table = itsData.itsExceptionTable; + if (table == null) { + if (top != 0) Kit.codeBug(); + table = new int[EXCEPTION_SLOT_SIZE * 2]; + itsData.itsExceptionTable = table; + } else if (table.length == top) { + table = new int[table.length * 2]; + System.arraycopy(itsData.itsExceptionTable, 0, table, 0, top); + itsData.itsExceptionTable = table; + } + table[top + EXCEPTION_TRY_START_SLOT] = icodeStart; + table[top + EXCEPTION_TRY_END_SLOT] = icodeEnd; + table[top + EXCEPTION_HANDLER_SLOT] = handlerStart; + table[top + EXCEPTION_TYPE_SLOT] = isFinally ? 1 : 0; + table[top + EXCEPTION_LOCAL_SLOT] = exceptionObjectLocal; + table[top + EXCEPTION_SCOPE_SLOT] = scopeLocal; + + itsExceptionTableTop = top + EXCEPTION_SLOT_SIZE; + } + + private byte[] increaseICodeCapacity(int extraSize) + { + int capacity = itsData.itsICode.length; + int top = itsICodeTop; + if (top + extraSize <= capacity) throw Kit.codeBug(); + capacity *= 2; + if (top + extraSize > capacity) { + capacity = top + extraSize; + } + byte[] array = new byte[capacity]; + System.arraycopy(itsData.itsICode, 0, array, 0, top); + itsData.itsICode = array; + return array; + } + + private void stackChange(int change) + { + if (change <= 0) { + itsStackDepth += change; + } else { + int newDepth = itsStackDepth + change; + if (newDepth > itsData.itsMaxStack) { + itsData.itsMaxStack = newDepth; + } + itsStackDepth = newDepth; + } + } + + private int allocLocal() + { + int localSlot = itsLocalTop; + ++itsLocalTop; + if (itsLocalTop > itsData.itsMaxLocals) { + itsData.itsMaxLocals = itsLocalTop; + } + return localSlot; + } + + private void releaseLocal(int localSlot) + { + --itsLocalTop; + if (localSlot != itsLocalTop) Kit.codeBug(); + } + + private static int getShort(byte[] iCode, int pc) { + return (iCode[pc] << 8) | (iCode[pc + 1] & 0xFF); + } + + private static int getIndex(byte[] iCode, int pc) { + return ((iCode[pc] & 0xFF) << 8) | (iCode[pc + 1] & 0xFF); + } + + private static int getInt(byte[] iCode, int pc) { + return (iCode[pc] << 24) | ((iCode[pc + 1] & 0xFF) << 16) + | ((iCode[pc + 2] & 0xFF) << 8) | (iCode[pc + 3] & 0xFF); + } + + private static int getExceptionHandler(CallFrame frame, + boolean onlyFinally) + { + int[] exceptionTable = frame.idata.itsExceptionTable; + if (exceptionTable == null) { + // No exception handlers + return -1; + } + + // Icode switch in the interpreter increments PC immediately + // and it is necessary to subtract 1 from the saved PC + // to point it before the start of the next instruction. + int pc = frame.pc - 1; + + // OPT: use binary search + int best = -1, bestStart = 0, bestEnd = 0; + for (int i = 0; i != exceptionTable.length; i += EXCEPTION_SLOT_SIZE) { + int start = exceptionTable[i + EXCEPTION_TRY_START_SLOT]; + int end = exceptionTable[i + EXCEPTION_TRY_END_SLOT]; + if (!(start <= pc && pc < end)) { + continue; + } + if (onlyFinally && exceptionTable[i + EXCEPTION_TYPE_SLOT] != 1) { + continue; + } + if (best >= 0) { + // Since handlers always nest and they never have shared end + // although they can share start it is sufficient to compare + // handlers ends + if (bestEnd < end) { + continue; + } + // Check the above assumption + if (bestStart > start) Kit.codeBug(); // should be nested + if (bestEnd == end) Kit.codeBug(); // no ens sharing + } + best = i; + bestStart = start; + bestEnd = end; + } + return best; + } + + private static void dumpICode(InterpreterData idata) + { + if (!Token.printICode) { + return; + } + + byte iCode[] = idata.itsICode; + int iCodeLength = iCode.length; + String[] strings = idata.itsStringTable; + PrintStream out = System.out; + out.println("ICode dump, for " + idata.itsName + + ", length = " + iCodeLength); + out.println("MaxStack = " + idata.itsMaxStack); + + int indexReg = 0; + for (int pc = 0; pc < iCodeLength; ) { + out.flush(); + out.print(" [" + pc + "] "); + int token = iCode[pc]; + int icodeLength = bytecodeSpan(token); + String tname = bytecodeName(token); + int old_pc = pc; + ++pc; + switch (token) { + default: + if (icodeLength != 1) Kit.codeBug(); + out.println(tname); + break; + + case Icode_GOSUB : + case Token.GOTO : + case Token.IFEQ : + case Token.IFNE : + case Icode_IFEQ_POP : + case Icode_LEAVEDQ : { + int newPC = pc + getShort(iCode, pc) - 1; + out.println(tname + " " + newPC); + pc += 2; + break; + } + case Icode_VAR_INC_DEC : + case Icode_NAME_INC_DEC : + case Icode_PROP_INC_DEC : + case Icode_ELEM_INC_DEC : + case Icode_REF_INC_DEC: { + int incrDecrType = iCode[pc]; + out.println(tname + " " + incrDecrType); + ++pc; + break; + } + + case Icode_CALLSPECIAL : { + int callType = iCode[pc] & 0xFF; + boolean isNew = (iCode[pc + 1] != 0); + int line = getIndex(iCode, pc+2); + out.println(tname+" "+callType+" "+isNew+" "+indexReg+" "+line); + pc += 4; + break; + } + + case Token.CATCH_SCOPE: + { + boolean afterFisrtFlag = (iCode[pc] != 0); + out.println(tname+" "+afterFisrtFlag); + ++pc; + } + break; + case Token.REGEXP : + out.println(tname+" "+idata.itsRegExpLiterals[indexReg]); + break; + case Token.OBJECTLIT : + case Icode_SPARE_ARRAYLIT : + out.println(tname+" "+idata.literalIds[indexReg]); + break; + case Icode_CLOSURE_EXPR : + case Icode_CLOSURE_STMT : + out.println(tname+" "+idata.itsNestedFunctions[indexReg]); + break; + case Token.CALL : + case Icode_TAIL_CALL : + case Token.REF_CALL : + case Token.NEW : + out.println(tname+' '+indexReg); + break; + case Token.THROW : + case Token.YIELD : + case Icode_GENERATOR : + case Icode_GENERATOR_END : + { + int line = getIndex(iCode, pc); + out.println(tname + " : " + line); + pc += 2; + break; + } + case Icode_SHORTNUMBER : { + int value = getShort(iCode, pc); + out.println(tname + " " + value); + pc += 2; + break; + } + case Icode_INTNUMBER : { + int value = getInt(iCode, pc); + out.println(tname + " " + value); + pc += 4; + break; + } + case Token.NUMBER : { + double value = idata.itsDoubleTable[indexReg]; + out.println(tname + " " + value); + break; + } + case Icode_LINE : { + int line = getIndex(iCode, pc); + out.println(tname + " : " + line); + pc += 2; + break; + } + case Icode_REG_STR1: { + String str = strings[0xFF & iCode[pc]]; + out.println(tname + " \"" + str + '"'); + ++pc; + break; + } + case Icode_REG_STR2: { + String str = strings[getIndex(iCode, pc)]; + out.println(tname + " \"" + str + '"'); + pc += 2; + break; + } + case Icode_REG_STR4: { + String str = strings[getInt(iCode, pc)]; + out.println(tname + " \"" + str + '"'); + pc += 4; + break; + } + case Icode_REG_IND_C0: + indexReg = 0; + out.println(tname); + break; + case Icode_REG_IND_C1: + indexReg = 1; + out.println(tname); + break; + case Icode_REG_IND_C2: + indexReg = 2; + out.println(tname); + break; + case Icode_REG_IND_C3: + indexReg = 3; + out.println(tname); + break; + case Icode_REG_IND_C4: + indexReg = 4; + out.println(tname); + break; + case Icode_REG_IND_C5: + indexReg = 5; + out.println(tname); + break; + case Icode_REG_IND1: { + indexReg = 0xFF & iCode[pc]; + out.println(tname+" "+indexReg); + ++pc; + break; + } + case Icode_REG_IND2: { + indexReg = getIndex(iCode, pc); + out.println(tname+" "+indexReg); + pc += 2; + break; + } + case Icode_REG_IND4: { + indexReg = getInt(iCode, pc); + out.println(tname+" "+indexReg); + pc += 4; + break; + } + case Icode_GETVAR1: + case Icode_SETVAR1: + case Icode_SETCONSTVAR1: + indexReg = iCode[pc]; + out.println(tname+" "+indexReg); + ++pc; + break; + } + if (old_pc + icodeLength != pc) Kit.codeBug(); + } + + int[] table = idata.itsExceptionTable; + if (table != null) { + out.println("Exception handlers: " + +table.length / EXCEPTION_SLOT_SIZE); + for (int i = 0; i != table.length; + i += EXCEPTION_SLOT_SIZE) + { + int tryStart = table[i + EXCEPTION_TRY_START_SLOT]; + int tryEnd = table[i + EXCEPTION_TRY_END_SLOT]; + int handlerStart = table[i + EXCEPTION_HANDLER_SLOT]; + int type = table[i + EXCEPTION_TYPE_SLOT]; + int exceptionLocal = table[i + EXCEPTION_LOCAL_SLOT]; + int scopeLocal = table[i + EXCEPTION_SCOPE_SLOT]; + + out.println(" tryStart="+tryStart+" tryEnd="+tryEnd + +" handlerStart="+handlerStart + +" type="+(type == 0 ? "catch" : "finally") + +" exceptionLocal="+exceptionLocal); + } + } + out.flush(); + } + + private static int bytecodeSpan(int bytecode) + { + switch (bytecode) { + case Token.THROW : + case Token.YIELD: + case Icode_GENERATOR: + case Icode_GENERATOR_END: + // source line + return 1 + 2; + + case Icode_GOSUB : + case Token.GOTO : + case Token.IFEQ : + case Token.IFNE : + case Icode_IFEQ_POP : + case Icode_LEAVEDQ : + // target pc offset + return 1 + 2; + + case Icode_CALLSPECIAL : + // call type + // is new + // line number + return 1 + 1 + 1 + 2; + + case Token.CATCH_SCOPE: + // scope flag + return 1 + 1; + + case Icode_VAR_INC_DEC: + case Icode_NAME_INC_DEC: + case Icode_PROP_INC_DEC: + case Icode_ELEM_INC_DEC: + case Icode_REF_INC_DEC: + // type of ++/-- + return 1 + 1; + + case Icode_SHORTNUMBER : + // short number + return 1 + 2; + + case Icode_INTNUMBER : + // int number + return 1 + 4; + + case Icode_REG_IND1: + // ubyte index + return 1 + 1; + + case Icode_REG_IND2: + // ushort index + return 1 + 2; + + case Icode_REG_IND4: + // int index + return 1 + 4; + + case Icode_REG_STR1: + // ubyte string index + return 1 + 1; + + case Icode_REG_STR2: + // ushort string index + return 1 + 2; + + case Icode_REG_STR4: + // int string index + return 1 + 4; + + case Icode_GETVAR1: + case Icode_SETVAR1: + case Icode_SETCONSTVAR1: + // byte var index + return 1 + 1; + + case Icode_LINE : + // line number + return 1 + 2; + } + if (!validBytecode(bytecode)) throw Kit.codeBug(); + return 1; + } + + static int[] getLineNumbers(InterpreterData data) + { + UintMap presentLines = new UintMap(); + + byte[] iCode = data.itsICode; + int iCodeLength = iCode.length; + for (int pc = 0; pc != iCodeLength;) { + int bytecode = iCode[pc]; + int span = bytecodeSpan(bytecode); + if (bytecode == Icode_LINE) { + if (span != 3) Kit.codeBug(); + int line = getIndex(iCode, pc + 1); + presentLines.put(line, 0); + } + pc += span; + } + + return presentLines.getKeys(); + } + + public void captureStackInfo(RhinoException ex) + { + Context cx = Context.getCurrentContext(); + if (cx == null || cx.lastInterpreterFrame == null) { + // No interpreter invocations + ex.interpreterStackInfo = null; + ex.interpreterLineData = null; + return; + } + // has interpreter frame on the stack + CallFrame[] array; + if (cx.previousInterpreterInvocations == null + || cx.previousInterpreterInvocations.size() == 0) + { + array = new CallFrame[1]; + } else { + int previousCount = cx.previousInterpreterInvocations.size(); + if (cx.previousInterpreterInvocations.peek() + == cx.lastInterpreterFrame) + { + // It can happen if exception was generated after + // frame was pushed to cx.previousInterpreterInvocations + // but before assignment to cx.lastInterpreterFrame. + // In this case frames has to be ignored. + --previousCount; + } + array = new CallFrame[previousCount + 1]; + cx.previousInterpreterInvocations.toArray(array); + } + array[array.length - 1] = (CallFrame)cx.lastInterpreterFrame; + + int interpreterFrameCount = 0; + for (int i = 0; i != array.length; ++i) { + interpreterFrameCount += 1 + array[i].frameIndex; + } + + int[] linePC = new int[interpreterFrameCount]; + // Fill linePC with pc positions from all interpreter frames. + // Start from the most nested frame + int linePCIndex = interpreterFrameCount; + for (int i = array.length; i != 0;) { + --i; + CallFrame frame = array[i]; + while (frame != null) { + --linePCIndex; + linePC[linePCIndex] = frame.pcSourceLineStart; + frame = frame.parentFrame; + } + } + if (linePCIndex != 0) Kit.codeBug(); + + ex.interpreterStackInfo = array; + ex.interpreterLineData = linePC; + } + + public String getSourcePositionFromStack(Context cx, int[] linep) + { + CallFrame frame = (CallFrame)cx.lastInterpreterFrame; + InterpreterData idata = frame.idata; + if (frame.pcSourceLineStart >= 0) { + linep[0] = getIndex(idata.itsICode, frame.pcSourceLineStart); + } else { + linep[0] = 0; + } + return idata.itsSourceFile; + } + + public String getPatchedStack(RhinoException ex, + String nativeStackTrace) + { + String tag = "org.mozilla.javascript.Interpreter.interpretLoop"; + StringBuffer sb = new StringBuffer(nativeStackTrace.length() + 1000); + String lineSeparator = SecurityUtilities.getSystemProperty("line.separator"); + + CallFrame[] array = (CallFrame[])ex.interpreterStackInfo; + int[] linePC = ex.interpreterLineData; + int arrayIndex = array.length; + int linePCIndex = linePC.length; + int offset = 0; + while (arrayIndex != 0) { + --arrayIndex; + int pos = nativeStackTrace.indexOf(tag, offset); + if (pos < 0) { + break; + } + + // Skip tag length + pos += tag.length(); + // Skip until the end of line + for (; pos != nativeStackTrace.length(); ++pos) { + char c = nativeStackTrace.charAt(pos); + if (c == '\n' || c == '\r') { + break; + } + } + sb.append(nativeStackTrace.substring(offset, pos)); + offset = pos; + + CallFrame frame = array[arrayIndex]; + while (frame != null) { + if (linePCIndex == 0) Kit.codeBug(); + --linePCIndex; + InterpreterData idata = frame.idata; + sb.append(lineSeparator); + sb.append("\tat script"); + if (idata.itsName != null && idata.itsName.length() != 0) { + sb.append('.'); + sb.append(idata.itsName); + } + sb.append('('); + sb.append(idata.itsSourceFile); + int pc = linePC[linePCIndex]; + if (pc >= 0) { + // Include line info only if available + sb.append(':'); + sb.append(getIndex(idata.itsICode, pc)); + } + sb.append(')'); + frame = frame.parentFrame; + } + } + sb.append(nativeStackTrace.substring(offset)); + + return sb.toString(); + } + + public List getScriptStack(RhinoException ex) + { + if (ex.interpreterStackInfo == null) { + return null; + } + + List list = new ArrayList(); + String lineSeparator = + SecurityUtilities.getSystemProperty("line.separator"); + + CallFrame[] array = (CallFrame[])ex.interpreterStackInfo; + int[] linePC = ex.interpreterLineData; + int arrayIndex = array.length; + int linePCIndex = linePC.length; + while (arrayIndex != 0) { + --arrayIndex; + StringBuffer sb = new StringBuffer(); + CallFrame frame = array[arrayIndex]; + while (frame != null) { + if (linePCIndex == 0) Kit.codeBug(); + --linePCIndex; + InterpreterData idata = frame.idata; + sb.append("\tat "); + sb.append(idata.itsSourceFile); + int pc = linePC[linePCIndex]; + if (pc >= 0) { + // Include line info only if available + sb.append(':'); + sb.append(getIndex(idata.itsICode, pc)); + } + if (idata.itsName != null && idata.itsName.length() != 0) { + sb.append(" ("); + sb.append(idata.itsName); + sb.append(')'); + } + sb.append(lineSeparator); + frame = frame.parentFrame; + } + list.add(sb.toString()); + } + return list; + } + + static String getEncodedSource(InterpreterData idata) + { + if (idata.encodedSource == null) { + return null; + } + return idata.encodedSource.substring(idata.encodedSourceStart, + idata.encodedSourceEnd); + } + + private static void initFunction(Context cx, Scriptable scope, + InterpretedFunction parent, int index) + { + InterpretedFunction fn; + fn = InterpretedFunction.createFunction(cx, scope, parent, index); + ScriptRuntime.initFunction(cx, scope, fn, fn.idata.itsFunctionType, + parent.idata.evalScriptFlag); + } + + static Object interpret(InterpretedFunction ifun, + Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!ScriptRuntime.hasTopCall(cx)) Kit.codeBug(); + + if (cx.interpreterSecurityDomain != ifun.securityDomain) { + Object savedDomain = cx.interpreterSecurityDomain; + cx.interpreterSecurityDomain = ifun.securityDomain; + try { + return ifun.securityController.callWithDomain( + ifun.securityDomain, cx, ifun, scope, thisObj, args); + } finally { + cx.interpreterSecurityDomain = savedDomain; + } + } + + CallFrame frame = new CallFrame(); + initFrame(cx, scope, thisObj, args, null, 0, args.length, + ifun, null, frame); + + return interpretLoop(cx, frame, null); + } + + static class GeneratorState { + GeneratorState(int operation, Object value) { + this.operation = operation; + this.value = value; + } + int operation; + Object value; + RuntimeException returnedException; + } + + public static Object resumeGenerator(Context cx, + Scriptable scope, + int operation, + Object savedState, + Object value) + { + CallFrame frame = (CallFrame) savedState; + GeneratorState generatorState = new GeneratorState(operation, value); + if (operation == NativeGenerator.GENERATOR_CLOSE) { + try { + return interpretLoop(cx, frame, generatorState); + } catch (RuntimeException e) { + // Only propagate exceptions other than closingException + if (e != value) + throw e; + } + return Undefined.instance; + } + Object result = interpretLoop(cx, frame, generatorState); + if (generatorState.returnedException != null) + throw generatorState.returnedException; + return result; + } + + public static Object restartContinuation(Continuation c, Context cx, + Scriptable scope, Object[] args) + { + if (!ScriptRuntime.hasTopCall(cx)) { + return ScriptRuntime.doTopCall(c, cx, scope, null, args); + } + + Object arg; + if (args.length == 0) { + arg = Undefined.instance; + } else { + arg = args[0]; + } + + CallFrame capturedFrame = (CallFrame)c.getImplementation(); + if (capturedFrame == null) { + // No frames to restart + return arg; + } + + ContinuationJump cjump = new ContinuationJump(c, null); + + cjump.result = arg; + return interpretLoop(cx, null, cjump); + } + + private static Object interpretLoop(Context cx, CallFrame frame, + Object throwable) + { + // throwable holds exception object to rethrow or catch + // It is also used for continuation restart in which case + // it holds ContinuationJump + + final Object DBL_MRK = UniqueTag.DOUBLE_MARK; + final Object undefined = Undefined.instance; + + final boolean instructionCounting = (cx.instructionThreshold != 0); + // arbitrary number to add to instructionCount when calling + // other functions + final int INVOCATION_COST = 100; + // arbitrary exception cost for instruction counting + final int EXCEPTION_COST = 100; + + String stringReg = null; + int indexReg = -1; + + if (cx.lastInterpreterFrame != null) { + // save the top frame from the previous interpretLoop + // invocation on the stack + if (cx.previousInterpreterInvocations == null) { + cx.previousInterpreterInvocations = new ObjArray(); + } + cx.previousInterpreterInvocations.push(cx.lastInterpreterFrame); + } + + // When restarting continuation throwable is not null and to jump + // to the code that rewind continuation state indexReg should be set + // to -1. + // With the normal call throable == null and indexReg == -1 allows to + // catch bugs with using indeReg to access array eleemnts before + // initializing indexReg. + + GeneratorState generatorState = null; + if (throwable != null) { + if (throwable instanceof GeneratorState) { + generatorState = (GeneratorState) throwable; + + // reestablish this call frame + enterFrame(cx, frame, ScriptRuntime.emptyArgs, true); + throwable = null; + } else if (!(throwable instanceof ContinuationJump)) { + // It should be continuation + Kit.codeBug(); + } + } + + Object interpreterResult = null; + double interpreterResultDbl = 0.0; + + StateLoop: for (;;) { + withoutExceptions: try { + + if (throwable != null) { + // Need to return both 'frame' and 'throwable' from + // 'processThrowable', so just added a 'throwable' + // member in 'frame'. + frame = processThrowable(cx, throwable, frame, indexReg, + instructionCounting); + throwable = frame.throwable; + frame.throwable = null; + } else { + if (generatorState == null && frame.frozen) Kit.codeBug(); + } + + // Use local variables for constant values in frame + // for faster access + Object[] stack = frame.stack; + double[] sDbl = frame.sDbl; + Object[] vars = frame.varSource.stack; + double[] varDbls = frame.varSource.sDbl; + int[] varAttributes = frame.varSource.stackAttributes; + byte[] iCode = frame.idata.itsICode; + String[] strings = frame.idata.itsStringTable; + + // Use local for stackTop as well. Since execption handlers + // can only exist at statement level where stack is empty, + // it is necessary to save/restore stackTop only across + // function calls and normal returns. + int stackTop = frame.savedStackTop; + + // Store new frame in cx which is used for error reporting etc. + cx.lastInterpreterFrame = frame; + + Loop: for (;;) { + + // Exception handler assumes that PC is already incremented + // pass the instruction start when it searches the + // exception handler + int op = iCode[frame.pc++]; + jumplessRun: { + + // Back indent to ease implementation reading +switch (op) { + case Icode_GENERATOR: { + if (!frame.frozen) { + // First time encountering this opcode: create new generator + // object and return + frame.pc--; // we want to come back here when we resume + CallFrame generatorFrame = captureFrameForGenerator(frame); + generatorFrame.frozen = true; + NativeGenerator generator = new NativeGenerator(frame.scope, + generatorFrame.fnOrScript, generatorFrame); + frame.result = generator; + break Loop; + } else { + // We are now resuming execution. Fall through to YIELD case. + } + } + // fall through... + case Token.YIELD: { + if (!frame.frozen) { + return freezeGenerator(cx, frame, stackTop, generatorState); + } else { + Object obj = thawGenerator(frame, stackTop, generatorState, op); + if (obj != Scriptable.NOT_FOUND) { + throwable = obj; + break withoutExceptions; + } + continue Loop; + } + } + case Icode_GENERATOR_END: { + // throw StopIteration + frame.frozen = true; + int sourceLine = getIndex(iCode, frame.pc); + generatorState.returnedException = new JavaScriptException( + NativeIterator.getStopIterationObject(frame.scope), + frame.idata.itsSourceFile, sourceLine); + break Loop; + } + case Token.THROW: { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + + int sourceLine = getIndex(iCode, frame.pc); + throwable = new JavaScriptException(value, + frame.idata.itsSourceFile, + sourceLine); + break withoutExceptions; + } + case Token.RETHROW: { + indexReg += frame.localShift; + throwable = stack[indexReg]; + break withoutExceptions; + } + case Token.GE : + case Token.LE : + case Token.GT : + case Token.LT : { + --stackTop; + Object rhs = stack[stackTop + 1]; + Object lhs = stack[stackTop]; + boolean valBln; + object_compare: + { + number_compare: + { + double rDbl, lDbl; + if (rhs == DBL_MRK) { + rDbl = sDbl[stackTop + 1]; + lDbl = stack_double(frame, stackTop); + } else if (lhs == DBL_MRK) { + rDbl = ScriptRuntime.toNumber(rhs); + lDbl = sDbl[stackTop]; + } else { + break number_compare; + } + switch (op) { + case Token.GE: + valBln = (lDbl >= rDbl); + break object_compare; + case Token.LE: + valBln = (lDbl <= rDbl); + break object_compare; + case Token.GT: + valBln = (lDbl > rDbl); + break object_compare; + case Token.LT: + valBln = (lDbl < rDbl); + break object_compare; + default: + throw Kit.codeBug(); + } + } + switch (op) { + case Token.GE: + valBln = ScriptRuntime.cmp_LE(rhs, lhs); + break; + case Token.LE: + valBln = ScriptRuntime.cmp_LE(lhs, rhs); + break; + case Token.GT: + valBln = ScriptRuntime.cmp_LT(rhs, lhs); + break; + case Token.LT: + valBln = ScriptRuntime.cmp_LT(lhs, rhs); + break; + default: + throw Kit.codeBug(); + } + } + stack[stackTop] = ScriptRuntime.wrapBoolean(valBln); + continue Loop; + } + case Token.IN : + case Token.INSTANCEOF : { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + boolean valBln; + if (op == Token.IN) { + valBln = ScriptRuntime.in(lhs, rhs, cx); + } else { + valBln = ScriptRuntime.instanceOf(lhs, rhs, cx); + } + stack[stackTop] = ScriptRuntime.wrapBoolean(valBln); + continue Loop; + } + case Token.EQ : + case Token.NE : { + --stackTop; + boolean valBln; + Object rhs = stack[stackTop + 1]; + Object lhs = stack[stackTop]; + if (rhs == DBL_MRK) { + if (lhs == DBL_MRK) { + valBln = (sDbl[stackTop] == sDbl[stackTop + 1]); + } else { + valBln = ScriptRuntime.eqNumber(sDbl[stackTop + 1], lhs); + } + } else { + if (lhs == DBL_MRK) { + valBln = ScriptRuntime.eqNumber(sDbl[stackTop], rhs); + } else { + valBln = ScriptRuntime.eq(lhs, rhs); + } + } + valBln ^= (op == Token.NE); + stack[stackTop] = ScriptRuntime.wrapBoolean(valBln); + continue Loop; + } + case Token.SHEQ : + case Token.SHNE : { + --stackTop; + Object rhs = stack[stackTop + 1]; + Object lhs = stack[stackTop]; + boolean valBln; + shallow_compare: { + double rdbl, ldbl; + if (rhs == DBL_MRK) { + rdbl = sDbl[stackTop + 1]; + if (lhs == DBL_MRK) { + ldbl = sDbl[stackTop]; + } else if (lhs instanceof Number) { + ldbl = ((Number)lhs).doubleValue(); + } else { + valBln = false; + break shallow_compare; + } + } else if (lhs == DBL_MRK) { + ldbl = sDbl[stackTop]; + if (rhs == DBL_MRK) { + rdbl = sDbl[stackTop + 1]; + } else if (rhs instanceof Number) { + rdbl = ((Number)rhs).doubleValue(); + } else { + valBln = false; + break shallow_compare; + } + } else { + valBln = ScriptRuntime.shallowEq(lhs, rhs); + break shallow_compare; + } + valBln = (ldbl == rdbl); + } + valBln ^= (op == Token.SHNE); + stack[stackTop] = ScriptRuntime.wrapBoolean(valBln); + continue Loop; + } + case Token.IFNE : + if (stack_boolean(frame, stackTop--)) { + frame.pc += 2; + continue Loop; + } + break jumplessRun; + case Token.IFEQ : + if (!stack_boolean(frame, stackTop--)) { + frame.pc += 2; + continue Loop; + } + break jumplessRun; + case Icode_IFEQ_POP : + if (!stack_boolean(frame, stackTop--)) { + frame.pc += 2; + continue Loop; + } + stack[stackTop--] = null; + break jumplessRun; + case Token.GOTO : + break jumplessRun; + case Icode_GOSUB : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = frame.pc + 2; + break jumplessRun; + case Icode_STARTSUB : + if (stackTop == frame.emptyStackTop + 1) { + // Call from Icode_GOSUB: store return PC address in the local + indexReg += frame.localShift; + stack[indexReg] = stack[stackTop]; + sDbl[indexReg] = sDbl[stackTop]; + --stackTop; + } else { + // Call from exception handler: exception object is already stored + // in the local + if (stackTop != frame.emptyStackTop) Kit.codeBug(); + } + continue Loop; + case Icode_RETSUB : { + // indexReg: local to store return address + if (instructionCounting) { + addInstructionCount(cx, frame, 0); + } + indexReg += frame.localShift; + Object value = stack[indexReg]; + if (value != DBL_MRK) { + // Invocation from exception handler, restore object to rethrow + throwable = value; + break withoutExceptions; + } + // Normal return from GOSUB + frame.pc = (int)sDbl[indexReg]; + if (instructionCounting) { + frame.pcPrevBranch = frame.pc; + } + continue Loop; + } + case Icode_POP : + stack[stackTop] = null; + stackTop--; + continue Loop; + case Icode_POP_RESULT : + frame.result = stack[stackTop]; + frame.resultDbl = sDbl[stackTop]; + stack[stackTop] = null; + --stackTop; + continue Loop; + case Icode_DUP : + stack[stackTop + 1] = stack[stackTop]; + sDbl[stackTop + 1] = sDbl[stackTop]; + stackTop++; + continue Loop; + case Icode_DUP2 : + stack[stackTop + 1] = stack[stackTop - 1]; + sDbl[stackTop + 1] = sDbl[stackTop - 1]; + stack[stackTop + 2] = stack[stackTop]; + sDbl[stackTop + 2] = sDbl[stackTop]; + stackTop += 2; + continue Loop; + case Icode_SWAP : { + Object o = stack[stackTop]; + stack[stackTop] = stack[stackTop - 1]; + stack[stackTop - 1] = o; + double d = sDbl[stackTop]; + sDbl[stackTop] = sDbl[stackTop - 1]; + sDbl[stackTop - 1] = d; + continue Loop; + } + case Token.RETURN : + frame.result = stack[stackTop]; + frame.resultDbl = sDbl[stackTop]; + --stackTop; + break Loop; + case Token.RETURN_RESULT : + break Loop; + case Icode_RETUNDEF : + frame.result = undefined; + break Loop; + case Token.BITNOT : { + int rIntValue = stack_int32(frame, stackTop); + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = ~rIntValue; + continue Loop; + } + case Token.BITAND : + case Token.BITOR : + case Token.BITXOR : + case Token.LSH : + case Token.RSH : { + int lIntValue = stack_int32(frame, stackTop-1); + int rIntValue = stack_int32(frame, stackTop); + stack[--stackTop] = DBL_MRK; + switch (op) { + case Token.BITAND: + lIntValue &= rIntValue; + break; + case Token.BITOR: + lIntValue |= rIntValue; + break; + case Token.BITXOR: + lIntValue ^= rIntValue; + break; + case Token.LSH: + lIntValue <<= rIntValue; + break; + case Token.RSH: + lIntValue >>= rIntValue; + break; + } + sDbl[stackTop] = lIntValue; + continue Loop; + } + case Token.URSH : { + double lDbl = stack_double(frame, stackTop-1); + int rIntValue = stack_int32(frame, stackTop) & 0x1F; + stack[--stackTop] = DBL_MRK; + sDbl[stackTop] = ScriptRuntime.toUint32(lDbl) >>> rIntValue; + continue Loop; + } + case Token.NEG : + case Token.POS : { + double rDbl = stack_double(frame, stackTop); + stack[stackTop] = DBL_MRK; + if (op == Token.NEG) { + rDbl = -rDbl; + } + sDbl[stackTop] = rDbl; + continue Loop; + } + case Token.ADD : + --stackTop; + do_add(stack, sDbl, stackTop, cx); + continue Loop; + case Token.SUB : + case Token.MUL : + case Token.DIV : + case Token.MOD : { + double rDbl = stack_double(frame, stackTop); + --stackTop; + double lDbl = stack_double(frame, stackTop); + stack[stackTop] = DBL_MRK; + switch (op) { + case Token.SUB: + lDbl -= rDbl; + break; + case Token.MUL: + lDbl *= rDbl; + break; + case Token.DIV: + lDbl /= rDbl; + break; + case Token.MOD: + lDbl %= rDbl; + break; + } + sDbl[stackTop] = lDbl; + continue Loop; + } + case Token.NOT : + stack[stackTop] = ScriptRuntime.wrapBoolean( + !stack_boolean(frame, stackTop)); + continue Loop; + case Token.BINDNAME : + stack[++stackTop] = ScriptRuntime.bind(cx, frame.scope, stringReg); + continue Loop; + case Token.SETNAME : { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Scriptable lhs = (Scriptable)stack[stackTop]; + stack[stackTop] = ScriptRuntime.setName(lhs, rhs, cx, + frame.scope, stringReg); + continue Loop; + } + case Icode_SETCONST: { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Scriptable lhs = (Scriptable)stack[stackTop]; + stack[stackTop] = ScriptRuntime.setConst(lhs, rhs, cx, stringReg); + continue Loop; + } + case Token.DELPROP : { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.delete(lhs, rhs, cx); + continue Loop; + } + case Token.GETPROPNOWARN : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.getObjectPropNoWarn(lhs, stringReg, cx); + continue Loop; + } + case Token.GETPROP : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.getObjectProp(lhs, stringReg, cx); + continue Loop; + } + case Token.SETPROP : { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.setObjectProp(lhs, stringReg, rhs, + cx); + continue Loop; + } + case Icode_PROP_INC_DEC : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.propIncrDecr(lhs, stringReg, + cx, iCode[frame.pc]); + ++frame.pc; + continue Loop; + } + case Token.GETELEM : { + --stackTop; + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) { + lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + } + Object value; + Object id = stack[stackTop + 1]; + if (id != DBL_MRK) { + value = ScriptRuntime.getObjectElem(lhs, id, cx); + } else { + double d = sDbl[stackTop + 1]; + value = ScriptRuntime.getObjectIndex(lhs, d, cx); + } + stack[stackTop] = value; + continue Loop; + } + case Token.SETELEM : { + stackTop -= 2; + Object rhs = stack[stackTop + 2]; + if (rhs == DBL_MRK) { + rhs = ScriptRuntime.wrapNumber(sDbl[stackTop + 2]); + } + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) { + lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + } + Object value; + Object id = stack[stackTop + 1]; + if (id != DBL_MRK) { + value = ScriptRuntime.setObjectElem(lhs, id, rhs, cx); + } else { + double d = sDbl[stackTop + 1]; + value = ScriptRuntime.setObjectIndex(lhs, d, rhs, cx); + } + stack[stackTop] = value; + continue Loop; + } + case Icode_ELEM_INC_DEC: { + Object rhs = stack[stackTop]; + if (rhs == DBL_MRK) rhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.elemIncrDecr(lhs, rhs, cx, + iCode[frame.pc]); + ++frame.pc; + continue Loop; + } + case Token.GET_REF : { + Ref ref = (Ref)stack[stackTop]; + stack[stackTop] = ScriptRuntime.refGet(ref, cx); + continue Loop; + } + case Token.SET_REF : { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Ref ref = (Ref)stack[stackTop]; + stack[stackTop] = ScriptRuntime.refSet(ref, value, cx); + continue Loop; + } + case Token.DEL_REF : { + Ref ref = (Ref)stack[stackTop]; + stack[stackTop] = ScriptRuntime.refDel(ref, cx); + continue Loop; + } + case Icode_REF_INC_DEC : { + Ref ref = (Ref)stack[stackTop]; + stack[stackTop] = ScriptRuntime.refIncrDecr(ref, cx, iCode[frame.pc]); + ++frame.pc; + continue Loop; + } + case Token.LOCAL_LOAD : + ++stackTop; + indexReg += frame.localShift; + stack[stackTop] = stack[indexReg]; + sDbl[stackTop] = sDbl[indexReg]; + continue Loop; + case Icode_LOCAL_CLEAR : + indexReg += frame.localShift; + stack[indexReg] = null; + continue Loop; + case Icode_NAME_AND_THIS : + // stringReg: name + ++stackTop; + stack[stackTop] = ScriptRuntime.getNameFunctionAndThis(stringReg, + cx, frame.scope); + ++stackTop; + stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + continue Loop; + case Icode_PROP_AND_THIS: { + Object obj = stack[stackTop]; + if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]); + // stringReg: property + stack[stackTop] = ScriptRuntime.getPropFunctionAndThis(obj, stringReg, + cx); + ++stackTop; + stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + continue Loop; + } + case Icode_ELEM_AND_THIS: { + Object obj = stack[stackTop - 1]; + if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop - 1]); + Object id = stack[stackTop]; + if (id == DBL_MRK) id = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop - 1] = ScriptRuntime.getElemFunctionAndThis(obj, id, cx); + stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + continue Loop; + } + case Icode_VALUE_AND_THIS : { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.getValueFunctionAndThis(value, cx); + ++stackTop; + stack[stackTop] = ScriptRuntime.lastStoredScriptable(cx); + continue Loop; + } + case Icode_CALLSPECIAL : { + if (instructionCounting) { + cx.instructionCount += INVOCATION_COST; + } + int callType = iCode[frame.pc] & 0xFF; + boolean isNew = (iCode[frame.pc + 1] != 0); + int sourceLine = getIndex(iCode, frame.pc + 2); + + // indexReg: number of arguments + if (isNew) { + // stack change: function arg0 .. argN -> newResult + stackTop -= indexReg; + + Object function = stack[stackTop]; + if (function == DBL_MRK) + function = ScriptRuntime.wrapNumber(sDbl[stackTop]); + Object[] outArgs = getArgsArray( + stack, sDbl, stackTop + 1, indexReg); + stack[stackTop] = ScriptRuntime.newSpecial( + cx, function, outArgs, frame.scope, callType); + } else { + // stack change: function thisObj arg0 .. argN -> result + stackTop -= 1 + indexReg; + + // Call code generation ensure that stack here + // is ... Callable Scriptable + Scriptable functionThis = (Scriptable)stack[stackTop + 1]; + Callable function = (Callable)stack[stackTop]; + Object[] outArgs = getArgsArray( + stack, sDbl, stackTop + 2, indexReg); + stack[stackTop] = ScriptRuntime.callSpecial( + cx, function, functionThis, outArgs, + frame.scope, frame.thisObj, callType, + frame.idata.itsSourceFile, sourceLine); + } + frame.pc += 4; + continue Loop; + } + case Token.CALL : + case Icode_TAIL_CALL : + case Token.REF_CALL : { + if (instructionCounting) { + cx.instructionCount += INVOCATION_COST; + } + // stack change: function thisObj arg0 .. argN -> result + // indexReg: number of arguments + stackTop -= 1 + indexReg; + + // CALL generation ensures that fun and funThisObj + // are already Scriptable and Callable objects respectively + Callable fun = (Callable)stack[stackTop]; + Scriptable funThisObj = (Scriptable)stack[stackTop + 1]; + if (op == Token.REF_CALL) { + Object[] outArgs = getArgsArray(stack, sDbl, stackTop + 2, + indexReg); + stack[stackTop] = ScriptRuntime.callRef(fun, funThisObj, + outArgs, cx); + continue Loop; + } + Scriptable calleeScope = frame.scope; + if (frame.useActivation) { + calleeScope = ScriptableObject.getTopLevelScope(frame.scope); + } + if (fun instanceof InterpretedFunction) { + InterpretedFunction ifun = (InterpretedFunction)fun; + if (frame.fnOrScript.securityDomain == ifun.securityDomain) { + CallFrame callParentFrame = frame; + CallFrame calleeFrame = new CallFrame(); + if (op == Icode_TAIL_CALL) { + // In principle tail call can re-use the current + // frame and its stack arrays but it is hard to + // do properly. Any exceptions that can legally + // happen during frame re-initialization including + // StackOverflowException during innocent looking + // System.arraycopy may leave the current frame + // data corrupted leading to undefined behaviour + // in the catch code bellow that unwinds JS stack + // on exceptions. Then there is issue about frame release + // end exceptions there. + // To avoid frame allocation a released frame + // can be cached for re-use which would also benefit + // non-tail calls but it is not clear that this caching + // would gain in performance due to potentially + // bad interaction with GC. + callParentFrame = frame.parentFrame; + // Release the current frame. See Bug #344501 to see why + // it is being done here. + exitFrame(cx, frame, null); + } + initFrame(cx, calleeScope, funThisObj, stack, sDbl, + stackTop + 2, indexReg, ifun, callParentFrame, + calleeFrame); + if (op != Icode_TAIL_CALL) { + frame.savedStackTop = stackTop; + frame.savedCallOp = op; + } + frame = calleeFrame; + continue StateLoop; + } + } + + if (fun instanceof Continuation) { + // Jump to the captured continuation + ContinuationJump cjump; + cjump = new ContinuationJump((Continuation)fun, frame); + + // continuation result is the first argument if any + // of contination call + if (indexReg == 0) { + cjump.result = undefined; + } else { + cjump.result = stack[stackTop + 2]; + cjump.resultDbl = sDbl[stackTop + 2]; + } + + // Start the real unwind job + throwable = cjump; + break withoutExceptions; + } + + if (fun instanceof IdFunctionObject) { + IdFunctionObject ifun = (IdFunctionObject)fun; + if (Continuation.isContinuationConstructor(ifun)) { + captureContinuation(cx, frame, stackTop); + continue Loop; + } + // Bug 405654 -- make best effort to keep Function.apply and + // Function.call within this interpreter loop invocation + if(BaseFunction.isApplyOrCall(ifun)) { + Callable applyCallable = ScriptRuntime.getCallable(funThisObj); + if(applyCallable instanceof InterpretedFunction) { + InterpretedFunction iApplyCallable = (InterpretedFunction)applyCallable; + if(frame.fnOrScript.securityDomain == iApplyCallable.securityDomain) { + frame = initFrameForApplyOrCall(cx, frame, indexReg, + stack, sDbl, stackTop, op, calleeScope, ifun, + iApplyCallable); + continue StateLoop; + } + } + } + } + + stack[stackTop] = fun.call(cx, calleeScope, funThisObj, + getArgsArray(stack, sDbl, stackTop + 2, indexReg)); + + continue Loop; + } + case Token.NEW : { + if (instructionCounting) { + cx.instructionCount += INVOCATION_COST; + } + // stack change: function arg0 .. argN -> newResult + // indexReg: number of arguments + stackTop -= indexReg; + + Object lhs = stack[stackTop]; + if (lhs instanceof InterpretedFunction) { + InterpretedFunction f = (InterpretedFunction)lhs; + if (frame.fnOrScript.securityDomain == f.securityDomain) { + Scriptable newInstance = f.createObject(cx, frame.scope); + CallFrame calleeFrame = new CallFrame(); + initFrame(cx, frame.scope, newInstance, stack, sDbl, + stackTop + 1, indexReg, f, frame, + calleeFrame); + + stack[stackTop] = newInstance; + frame.savedStackTop = stackTop; + frame.savedCallOp = op; + frame = calleeFrame; + continue StateLoop; + } + } + if (!(lhs instanceof Function)) { + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + throw ScriptRuntime.notFunctionError(lhs); + } + Function fun = (Function)lhs; + + if (fun instanceof IdFunctionObject) { + IdFunctionObject ifun = (IdFunctionObject)fun; + if (Continuation.isContinuationConstructor(ifun)) { + captureContinuation(cx, frame, stackTop); + continue Loop; + } + } + + Object[] outArgs = getArgsArray(stack, sDbl, stackTop + 1, indexReg); + stack[stackTop] = fun.construct(cx, frame.scope, outArgs); + continue Loop; + } + case Token.TYPEOF : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.typeof(lhs); + continue Loop; + } + case Icode_TYPEOFNAME : + stack[++stackTop] = ScriptRuntime.typeofName(frame.scope, stringReg); + continue Loop; + case Token.STRING : + stack[++stackTop] = stringReg; + continue Loop; + case Icode_SHORTNUMBER : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = getShort(iCode, frame.pc); + frame.pc += 2; + continue Loop; + case Icode_INTNUMBER : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = getInt(iCode, frame.pc); + frame.pc += 4; + continue Loop; + case Token.NUMBER : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = frame.idata.itsDoubleTable[indexReg]; + continue Loop; + case Token.NAME : + stack[++stackTop] = ScriptRuntime.name(cx, frame.scope, stringReg); + continue Loop; + case Icode_NAME_INC_DEC : + stack[++stackTop] = ScriptRuntime.nameIncrDecr(frame.scope, stringReg, + cx, iCode[frame.pc]); + ++frame.pc; + continue Loop; + case Icode_SETCONSTVAR1: + indexReg = iCode[frame.pc++]; + // fallthrough + case Token.SETCONSTVAR : + if (!frame.useActivation) { + if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) { + throw Context.reportRuntimeError1("msg.var.redecl", + frame.idata.argNames[indexReg]); + } + if ((varAttributes[indexReg] & ScriptableObject.UNINITIALIZED_CONST) + != 0) + { + vars[indexReg] = stack[stackTop]; + varAttributes[indexReg] &= ~ScriptableObject.UNINITIALIZED_CONST; + varDbls[indexReg] = sDbl[stackTop]; + } + } else { + Object val = stack[stackTop]; + if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stringReg = frame.idata.argNames[indexReg]; + if (frame.scope instanceof ConstProperties) { + ConstProperties cp = (ConstProperties)frame.scope; + cp.putConst(stringReg, frame.scope, val); + } else + throw Kit.codeBug(); + } + continue Loop; + case Icode_SETVAR1: + indexReg = iCode[frame.pc++]; + // fallthrough + case Token.SETVAR : + if (!frame.useActivation) { + if ((varAttributes[indexReg] & ScriptableObject.READONLY) == 0) { + vars[indexReg] = stack[stackTop]; + varDbls[indexReg] = sDbl[stackTop]; + } + } else { + Object val = stack[stackTop]; + if (val == DBL_MRK) val = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stringReg = frame.idata.argNames[indexReg]; + frame.scope.put(stringReg, frame.scope, val); + } + continue Loop; + case Icode_GETVAR1: + indexReg = iCode[frame.pc++]; + // fallthrough + case Token.GETVAR : + ++stackTop; + if (!frame.useActivation) { + stack[stackTop] = vars[indexReg]; + sDbl[stackTop] = varDbls[indexReg]; + } else { + stringReg = frame.idata.argNames[indexReg]; + stack[stackTop] = frame.scope.get(stringReg, frame.scope); + } + continue Loop; + case Icode_VAR_INC_DEC : { + // indexReg : varindex + ++stackTop; + int incrDecrMask = iCode[frame.pc]; + if (!frame.useActivation) { + stack[stackTop] = DBL_MRK; + Object varValue = vars[indexReg]; + double d; + if (varValue == DBL_MRK) { + d = varDbls[indexReg]; + } else { + d = ScriptRuntime.toNumber(varValue); + vars[indexReg] = DBL_MRK; + } + double d2 = ((incrDecrMask & Node.DECR_FLAG) == 0) + ? d + 1.0 : d - 1.0; + varDbls[indexReg] = d2; + sDbl[stackTop] = ((incrDecrMask & Node.POST_FLAG) == 0) ? d2 : d; + } else { + String varName = frame.idata.argNames[indexReg]; + stack[stackTop] = ScriptRuntime.nameIncrDecr(frame.scope, varName, + cx, incrDecrMask); + } + ++frame.pc; + continue Loop; + } + case Icode_ZERO : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = 0; + continue Loop; + case Icode_ONE : + ++stackTop; + stack[stackTop] = DBL_MRK; + sDbl[stackTop] = 1; + continue Loop; + case Token.NULL : + stack[++stackTop] = null; + continue Loop; + case Token.THIS : + stack[++stackTop] = frame.thisObj; + continue Loop; + case Token.THISFN : + stack[++stackTop] = frame.fnOrScript; + continue Loop; + case Token.FALSE : + stack[++stackTop] = Boolean.FALSE; + continue Loop; + case Token.TRUE : + stack[++stackTop] = Boolean.TRUE; + continue Loop; + case Icode_UNDEF : + stack[++stackTop] = undefined; + continue Loop; + case Token.ENTERWITH : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + frame.scope = ScriptRuntime.enterWith(lhs, cx, frame.scope); + continue Loop; + } + case Token.LEAVEWITH : + frame.scope = ScriptRuntime.leaveWith(frame.scope); + continue Loop; + case Token.CATCH_SCOPE : { + // stack top: exception object + // stringReg: name of exception variable + // indexReg: local for exception scope + --stackTop; + indexReg += frame.localShift; + + boolean afterFirstScope = (frame.idata.itsICode[frame.pc] != 0); + Throwable caughtException = (Throwable)stack[stackTop + 1]; + Scriptable lastCatchScope; + if (!afterFirstScope) { + lastCatchScope = null; + } else { + lastCatchScope = (Scriptable)stack[indexReg]; + } + stack[indexReg] = ScriptRuntime.newCatchScope(caughtException, + lastCatchScope, stringReg, + cx, frame.scope); + ++frame.pc; + continue Loop; + } + case Token.ENUM_INIT_KEYS : + case Token.ENUM_INIT_VALUES : + case Token.ENUM_INIT_ARRAY : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + indexReg += frame.localShift; + int enumType = op == Token.ENUM_INIT_KEYS + ? ScriptRuntime.ENUMERATE_KEYS : + op == Token.ENUM_INIT_VALUES + ? ScriptRuntime.ENUMERATE_VALUES : + ScriptRuntime.ENUMERATE_ARRAY; + stack[indexReg] = ScriptRuntime.enumInit(lhs, cx, enumType); + continue Loop; + } + case Token.ENUM_NEXT : + case Token.ENUM_ID : { + indexReg += frame.localShift; + Object val = stack[indexReg]; + ++stackTop; + stack[stackTop] = (op == Token.ENUM_NEXT) + ? (Object)ScriptRuntime.enumNext(val) + : (Object)ScriptRuntime.enumId(val, cx); + continue Loop; + } + case Token.REF_SPECIAL : { + //stringReg: name of special property + Object obj = stack[stackTop]; + if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.specialRef(obj, stringReg, cx); + continue Loop; + } + case Token.REF_MEMBER: { + //indexReg: flags + Object elem = stack[stackTop]; + if (elem == DBL_MRK) elem = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object obj = stack[stackTop]; + if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.memberRef(obj, elem, cx, indexReg); + continue Loop; + } + case Token.REF_NS_MEMBER: { + //indexReg: flags + Object elem = stack[stackTop]; + if (elem == DBL_MRK) elem = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object ns = stack[stackTop]; + if (ns == DBL_MRK) ns = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object obj = stack[stackTop]; + if (obj == DBL_MRK) obj = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.memberRef(obj, ns, elem, cx, indexReg); + continue Loop; + } + case Token.REF_NAME: { + //indexReg: flags + Object name = stack[stackTop]; + if (name == DBL_MRK) name = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.nameRef(name, cx, frame.scope, + indexReg); + continue Loop; + } + case Token.REF_NS_NAME: { + //indexReg: flags + Object name = stack[stackTop]; + if (name == DBL_MRK) name = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + Object ns = stack[stackTop]; + if (ns == DBL_MRK) ns = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.nameRef(ns, name, cx, frame.scope, + indexReg); + continue Loop; + } + case Icode_SCOPE_LOAD : + indexReg += frame.localShift; + frame.scope = (Scriptable)stack[indexReg]; + continue Loop; + case Icode_SCOPE_SAVE : + indexReg += frame.localShift; + stack[indexReg] = frame.scope; + continue Loop; + case Icode_CLOSURE_EXPR : + stack[++stackTop] = InterpretedFunction.createFunction(cx, frame.scope, + frame.fnOrScript, + indexReg); + continue Loop; + case Icode_CLOSURE_STMT : + initFunction(cx, frame.scope, frame.fnOrScript, indexReg); + continue Loop; + case Token.REGEXP : + stack[++stackTop] = frame.scriptRegExps[indexReg]; + continue Loop; + case Icode_LITERAL_NEW : + // indexReg: number of values in the literal + ++stackTop; + stack[stackTop] = new int[indexReg]; + ++stackTop; + stack[stackTop] = new Object[indexReg]; + sDbl[stackTop] = 0; + continue Loop; + case Icode_LITERAL_SET : { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + int i = (int)sDbl[stackTop]; + ((Object[])stack[stackTop])[i] = value; + sDbl[stackTop] = i + 1; + continue Loop; + } + case Icode_LITERAL_GETTER : { + Object value = stack[stackTop]; + --stackTop; + int i = (int)sDbl[stackTop]; + ((Object[])stack[stackTop])[i] = value; + ((int[])stack[stackTop - 1])[i] = -1; + sDbl[stackTop] = i + 1; + continue Loop; + } + case Icode_LITERAL_SETTER : { + Object value = stack[stackTop]; + --stackTop; + int i = (int)sDbl[stackTop]; + ((Object[])stack[stackTop])[i] = value; + ((int[])stack[stackTop - 1])[i] = +1; + sDbl[stackTop] = i + 1; + continue Loop; + } + case Token.ARRAYLIT : + case Icode_SPARE_ARRAYLIT : + case Token.OBJECTLIT : { + Object[] data = (Object[])stack[stackTop]; + --stackTop; + int[] getterSetters = (int[])stack[stackTop]; + Object val; + if (op == Token.OBJECTLIT) { + Object[] ids = (Object[])frame.idata.literalIds[indexReg]; + val = ScriptRuntime.newObjectLiteral(ids, data, getterSetters, cx, + frame.scope); + } else { + int[] skipIndexces = null; + if (op == Icode_SPARE_ARRAYLIT) { + skipIndexces = (int[])frame.idata.literalIds[indexReg]; + } + val = ScriptRuntime.newArrayLiteral(data, skipIndexces, cx, + frame.scope); + } + stack[stackTop] = val; + continue Loop; + } + case Icode_ENTERDQ : { + Object lhs = stack[stackTop]; + if (lhs == DBL_MRK) lhs = ScriptRuntime.wrapNumber(sDbl[stackTop]); + --stackTop; + frame.scope = ScriptRuntime.enterDotQuery(lhs, frame.scope); + continue Loop; + } + case Icode_LEAVEDQ : { + boolean valBln = stack_boolean(frame, stackTop); + Object x = ScriptRuntime.updateDotQuery(valBln, frame.scope); + if (x != null) { + stack[stackTop] = x; + frame.scope = ScriptRuntime.leaveDotQuery(frame.scope); + frame.pc += 2; + continue Loop; + } + // reset stack and PC to code after ENTERDQ + --stackTop; + break jumplessRun; + } + case Token.DEFAULTNAMESPACE : { + Object value = stack[stackTop]; + if (value == DBL_MRK) value = ScriptRuntime.wrapNumber(sDbl[stackTop]); + stack[stackTop] = ScriptRuntime.setDefaultNamespace(value, cx); + continue Loop; + } + case Token.ESCXMLATTR : { + Object value = stack[stackTop]; + if (value != DBL_MRK) { + stack[stackTop] = ScriptRuntime.escapeAttributeValue(value, cx); + } + continue Loop; + } + case Token.ESCXMLTEXT : { + Object value = stack[stackTop]; + if (value != DBL_MRK) { + stack[stackTop] = ScriptRuntime.escapeTextValue(value, cx); + } + continue Loop; + } + case Icode_DEBUGGER: + if (frame.debuggerFrame != null) { + frame.debuggerFrame.onDebuggerStatement(cx); + } + break Loop; + case Icode_LINE : + frame.pcSourceLineStart = frame.pc; + if (frame.debuggerFrame != null) { + int line = getIndex(iCode, frame.pc); + frame.debuggerFrame.onLineChange(cx, line); + } + frame.pc += 2; + continue Loop; + case Icode_REG_IND_C0: + indexReg = 0; + continue Loop; + case Icode_REG_IND_C1: + indexReg = 1; + continue Loop; + case Icode_REG_IND_C2: + indexReg = 2; + continue Loop; + case Icode_REG_IND_C3: + indexReg = 3; + continue Loop; + case Icode_REG_IND_C4: + indexReg = 4; + continue Loop; + case Icode_REG_IND_C5: + indexReg = 5; + continue Loop; + case Icode_REG_IND1: + indexReg = 0xFF & iCode[frame.pc]; + ++frame.pc; + continue Loop; + case Icode_REG_IND2: + indexReg = getIndex(iCode, frame.pc); + frame.pc += 2; + continue Loop; + case Icode_REG_IND4: + indexReg = getInt(iCode, frame.pc); + frame.pc += 4; + continue Loop; + case Icode_REG_STR_C0: + stringReg = strings[0]; + continue Loop; + case Icode_REG_STR_C1: + stringReg = strings[1]; + continue Loop; + case Icode_REG_STR_C2: + stringReg = strings[2]; + continue Loop; + case Icode_REG_STR_C3: + stringReg = strings[3]; + continue Loop; + case Icode_REG_STR1: + stringReg = strings[0xFF & iCode[frame.pc]]; + ++frame.pc; + continue Loop; + case Icode_REG_STR2: + stringReg = strings[getIndex(iCode, frame.pc)]; + frame.pc += 2; + continue Loop; + case Icode_REG_STR4: + stringReg = strings[getInt(iCode, frame.pc)]; + frame.pc += 4; + continue Loop; + default : + dumpICode(frame.idata); + throw new RuntimeException( + "Unknown icode : "+op+" @ pc : "+(frame.pc-1)); +} // end of interpreter switch + + } // end of jumplessRun label block + + // This should be reachable only for jump implementation + // when pc points to encoded target offset + if (instructionCounting) { + addInstructionCount(cx, frame, 2); + } + int offset = getShort(iCode, frame.pc); + if (offset != 0) { + // -1 accounts for pc pointing to jump opcode + 1 + frame.pc += offset - 1; + } else { + frame.pc = frame.idata.longJumps. + getExistingInt(frame.pc); + } + if (instructionCounting) { + frame.pcPrevBranch = frame.pc; + } + continue Loop; + + } // end of Loop: for + + exitFrame(cx, frame, null); + interpreterResult = frame.result; + interpreterResultDbl = frame.resultDbl; + if (frame.parentFrame != null) { + frame = frame.parentFrame; + if (frame.frozen) { + frame = frame.cloneFrozen(); + } + setCallResult( + frame, interpreterResult, interpreterResultDbl); + interpreterResult = null; // Help GC + continue StateLoop; + } + break StateLoop; + + } // end of interpreter withoutExceptions: try + catch (Throwable ex) { + if (throwable != null) { + // This is serious bug and it is better to track it ASAP + ex.printStackTrace(System.err); + throw new IllegalStateException(); + } + throwable = ex; + } + + // This should be reachable only after above catch or from + // finally when it needs to propagate exception or from + // explicit throw + if (throwable == null) Kit.codeBug(); + + // Exception type + final int EX_CATCH_STATE = 2; // Can execute JS catch + final int EX_FINALLY_STATE = 1; // Can execute JS finally + final int EX_NO_JS_STATE = 0; // Terminate JS execution + + int exState; + ContinuationJump cjump = null; + + if (generatorState != null && + generatorState.operation == NativeGenerator.GENERATOR_CLOSE && + throwable == generatorState.value) + { + exState = EX_FINALLY_STATE; + } else if (throwable instanceof JavaScriptException) { + exState = EX_CATCH_STATE; + } else if (throwable instanceof EcmaError) { + // an offical ECMA error object, + exState = EX_CATCH_STATE; + } else if (throwable instanceof EvaluatorException) { + exState = EX_CATCH_STATE; + } else if (throwable instanceof RuntimeException) { + exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) + ? EX_CATCH_STATE + : EX_FINALLY_STATE; + } else if (throwable instanceof Error) { + exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) + ? EX_CATCH_STATE + : EX_NO_JS_STATE; + } else if (throwable instanceof ContinuationJump) { + // It must be ContinuationJump + exState = EX_FINALLY_STATE; + cjump = (ContinuationJump)throwable; + } else { + exState = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) + ? EX_CATCH_STATE + : EX_FINALLY_STATE; + } + + if (instructionCounting) { + try { + addInstructionCount(cx, frame, EXCEPTION_COST); + } catch (RuntimeException ex) { + throwable = ex; + exState = EX_FINALLY_STATE; + } catch (Error ex) { + // Error from instruction counting + // => unconditionally terminate JS + throwable = ex; + cjump = null; + exState = EX_NO_JS_STATE; + } + } + if (frame.debuggerFrame != null + && throwable instanceof RuntimeException) + { + // Call debugger only for RuntimeException + RuntimeException rex = (RuntimeException)throwable; + try { + frame.debuggerFrame.onExceptionThrown(cx, rex); + } catch (Throwable ex) { + // Any exception from debugger + // => unconditionally terminate JS + throwable = ex; + cjump = null; + exState = EX_NO_JS_STATE; + } + } + + for (;;) { + if (exState != EX_NO_JS_STATE) { + boolean onlyFinally = (exState != EX_CATCH_STATE); + indexReg = getExceptionHandler(frame, onlyFinally); + if (indexReg >= 0) { + // We caught an exception, restart the loop + // with exception pending the processing at the loop + // start + continue StateLoop; + } + } + // No allowed exception handlers in this frame, unwind + // to parent and try to look there + + exitFrame(cx, frame, throwable); + + frame = frame.parentFrame; + if (frame == null) { break; } + if (cjump != null && cjump.branchFrame == frame) { + // Continuation branch point was hit, + // restart the state loop to reenter continuation + indexReg = -1; + continue StateLoop; + } + } + + // No more frames, rethrow the exception or deal with continuation + if (cjump != null) { + if (cjump.branchFrame != null) { + // The above loop should locate the top frame + Kit.codeBug(); + } + if (cjump.capturedFrame != null) { + // Restarting detached continuation + indexReg = -1; + continue StateLoop; + } + // Return continuation result to the caller + interpreterResult = cjump.result; + interpreterResultDbl = cjump.resultDbl; + throwable = null; + } + break StateLoop; + + } // end of StateLoop: for(;;) + + // Do cleanups/restorations before the final return or throw + + if (cx.previousInterpreterInvocations != null + && cx.previousInterpreterInvocations.size() != 0) + { + cx.lastInterpreterFrame + = cx.previousInterpreterInvocations.pop(); + } else { + // It was the last interpreter frame on the stack + cx.lastInterpreterFrame = null; + // Force GC of the value cx.previousInterpreterInvocations + cx.previousInterpreterInvocations = null; + } + + if (throwable != null) { + if (throwable instanceof RuntimeException) { + throw (RuntimeException)throwable; + } else { + // Must be instance of Error or code bug + throw (Error)throwable; + } + } + + return (interpreterResult != DBL_MRK) + ? interpreterResult + : ScriptRuntime.wrapNumber(interpreterResultDbl); + } + + private static CallFrame processThrowable(Context cx, Object throwable, + CallFrame frame, int indexReg, + boolean instructionCounting) + { + // Recovering from exception, indexReg contains + // the index of handler + + if (indexReg >= 0) { + // Normal exception handler, transfer + // control appropriately + + if (frame.frozen) { + // XXX Deal with exceptios!!! + frame = frame.cloneFrozen(); + } + + int[] table = frame.idata.itsExceptionTable; + + frame.pc = table[indexReg + EXCEPTION_HANDLER_SLOT]; + if (instructionCounting) { + frame.pcPrevBranch = frame.pc; + } + + frame.savedStackTop = frame.emptyStackTop; + int scopeLocal = frame.localShift + + table[indexReg + + EXCEPTION_SCOPE_SLOT]; + int exLocal = frame.localShift + + table[indexReg + + EXCEPTION_LOCAL_SLOT]; + frame.scope = (Scriptable)frame.stack[scopeLocal]; + frame.stack[exLocal] = throwable; + + throwable = null; + } else { + // Continuation restoration + ContinuationJump cjump = (ContinuationJump)throwable; + + // Clear throwable to indicate that exceptions are OK + throwable = null; + + if (cjump.branchFrame != frame) Kit.codeBug(); + + // Check that we have at least one frozen frame + // in the case of detached continuation restoration: + // unwind code ensure that + if (cjump.capturedFrame == null) Kit.codeBug(); + + // Need to rewind branchFrame, capturedFrame + // and all frames in between + int rewindCount = cjump.capturedFrame.frameIndex + 1; + if (cjump.branchFrame != null) { + rewindCount -= cjump.branchFrame.frameIndex; + } + + int enterCount = 0; + CallFrame[] enterFrames = null; + + CallFrame x = cjump.capturedFrame; + for (int i = 0; i != rewindCount; ++i) { + if (!x.frozen) Kit.codeBug(); + if (isFrameEnterExitRequired(x)) { + if (enterFrames == null) { + // Allocate enough space to store the rest + // of rewind frames in case all of them + // would require to enter + enterFrames = new CallFrame[rewindCount + - i]; + } + enterFrames[enterCount] = x; + ++enterCount; + } + x = x.parentFrame; + } + + while (enterCount != 0) { + // execute enter: walk enterFrames in the reverse + // order since they were stored starting from + // the capturedFrame, not branchFrame + --enterCount; + x = enterFrames[enterCount]; + enterFrame(cx, x, ScriptRuntime.emptyArgs, true); + } + + // Continuation jump is almost done: capturedFrame + // points to the call to the function that captured + // continuation, so clone capturedFrame and + // emulate return that function with the suplied result + frame = cjump.capturedFrame.cloneFrozen(); + setCallResult(frame, cjump.result, cjump.resultDbl); + // restart the execution + } + frame.throwable = throwable; + return frame; + } + + private static Object freezeGenerator(Context cx, CallFrame frame, + int stackTop, + GeneratorState generatorState) + { + if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) { + // Error: no yields when generator is closing + throw ScriptRuntime.typeError0("msg.yield.closing"); + } + // return to our caller (which should be a method of NativeGenerator) + frame.frozen = true; + frame.result = frame.stack[stackTop]; + frame.resultDbl = frame.sDbl[stackTop]; + frame.savedStackTop = stackTop; + frame.pc--; // we want to come back here when we resume + ScriptRuntime.exitActivationFunction(cx); + return (frame.result != UniqueTag.DOUBLE_MARK) + ? frame.result + : ScriptRuntime.wrapNumber(frame.resultDbl); + } + + private static Object thawGenerator(CallFrame frame, int stackTop, + GeneratorState generatorState, int op) + { + // we are resuming execution + frame.frozen = false; + int sourceLine = getIndex(frame.idata.itsICode, frame.pc); + frame.pc += 2; // skip line number data + if (generatorState.operation == NativeGenerator.GENERATOR_THROW) { + // processing a call to <generator>.throw(exception): must + // act as if exception was thrown from resumption point + return new JavaScriptException(generatorState.value, + frame.idata.itsSourceFile, + sourceLine); + } + if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) { + return generatorState.value; + } + if (generatorState.operation != NativeGenerator.GENERATOR_SEND) + throw Kit.codeBug(); + if (op == Token.YIELD) + frame.stack[stackTop] = generatorState.value; + return Scriptable.NOT_FOUND; + } + + private static CallFrame initFrameForApplyOrCall(Context cx, CallFrame frame, + int indexReg, Object[] stack, double[] sDbl, int stackTop, int op, + Scriptable calleeScope, IdFunctionObject ifun, + InterpretedFunction iApplyCallable) + { + Scriptable applyThis; + if (indexReg != 0) { + applyThis = ScriptRuntime.toObjectOrNull(cx, stack[stackTop + 2]); + } + else { + applyThis = null; + } + if (applyThis == null) { + // This covers the case of args[0] == (null|undefined) as well. + applyThis = ScriptRuntime.getTopCallScope(cx); + } + if(op == Icode_TAIL_CALL) { + exitFrame(cx, frame, null); + frame = frame.parentFrame; + } + else { + frame.savedStackTop = stackTop; + frame.savedCallOp = op; + } + CallFrame calleeFrame = new CallFrame(); + if(BaseFunction.isApply(ifun)) { + Object[] callArgs = indexReg < 2 ? ScriptRuntime.emptyArgs : + ScriptRuntime.getApplyArguments(cx, stack[stackTop + 3]); + initFrame(cx, calleeScope, applyThis, callArgs, null, 0, + callArgs.length, iApplyCallable, frame, calleeFrame); + } + else { + // Shift args left + for(int i = 1; i < indexReg; ++i) { + stack[stackTop + 1 + i] = stack[stackTop + 2 + i]; + sDbl[stackTop + 1 + i] = sDbl[stackTop + 2 + i]; + } + int argCount = indexReg < 2 ? 0 : indexReg - 1; + initFrame(cx, calleeScope, applyThis, stack, sDbl, stackTop + 2, + argCount, iApplyCallable, frame, calleeFrame); + } + + frame = calleeFrame; + return frame; + } + + private static void initFrame(Context cx, Scriptable callerScope, + Scriptable thisObj, + Object[] args, double[] argsDbl, + int argShift, int argCount, + InterpretedFunction fnOrScript, + CallFrame parentFrame, CallFrame frame) + { + InterpreterData idata = fnOrScript.idata; + + boolean useActivation = idata.itsNeedsActivation; + DebugFrame debuggerFrame = null; + if (cx.debugger != null) { + debuggerFrame = cx.debugger.getFrame(cx, idata); + if (debuggerFrame != null) { + useActivation = true; + } + } + + if (useActivation) { + // Copy args to new array to pass to enterActivationFunction + // or debuggerFrame.onEnter + if (argsDbl != null) { + args = getArgsArray(args, argsDbl, argShift, argCount); + } + argShift = 0; + argsDbl = null; + } + + Scriptable scope; + if (idata.itsFunctionType != 0) { + if (!idata.useDynamicScope) { + scope = fnOrScript.getParentScope(); + } else { + scope = callerScope; + } + + if (useActivation) { + scope = ScriptRuntime.createFunctionActivation( + fnOrScript, scope, args); + } + } else { + scope = callerScope; + ScriptRuntime.initScript(fnOrScript, thisObj, cx, scope, + fnOrScript.idata.evalScriptFlag); + } + + if (idata.itsNestedFunctions != null) { + if (idata.itsFunctionType != 0 && !idata.itsNeedsActivation) + Kit.codeBug(); + for (int i = 0; i < idata.itsNestedFunctions.length; i++) { + InterpreterData fdata = idata.itsNestedFunctions[i]; + if (fdata.itsFunctionType == FunctionNode.FUNCTION_STATEMENT) { + initFunction(cx, scope, fnOrScript, i); + } + } + } + + Scriptable[] scriptRegExps = null; + if (idata.itsRegExpLiterals != null) { + // Wrapped regexps for functions are stored in + // InterpretedFunction + // but for script which should not contain references to scope + // the regexps re-wrapped during each script execution + if (idata.itsFunctionType != 0) { + scriptRegExps = fnOrScript.functionRegExps; + } else { + scriptRegExps = fnOrScript.createRegExpWraps(cx, scope); + } + } + + // Initialize args, vars, locals and stack + + int emptyStackTop = idata.itsMaxVars + idata.itsMaxLocals - 1; + int maxFrameArray = idata.itsMaxFrameArray; + if (maxFrameArray != emptyStackTop + idata.itsMaxStack + 1) + Kit.codeBug(); + + Object[] stack; + int[] stackAttributes; + double[] sDbl; + boolean stackReuse; + if (frame.stack != null && maxFrameArray <= frame.stack.length) { + // Reuse stacks from old frame + stackReuse = true; + stack = frame.stack; + stackAttributes = frame.stackAttributes; + sDbl = frame.sDbl; + } else { + stackReuse = false; + stack = new Object[maxFrameArray]; + stackAttributes = new int[maxFrameArray]; + sDbl = new double[maxFrameArray]; + } + + int varCount = idata.getParamAndVarCount(); + for (int i = 0; i < varCount; i++) { + if (idata.getParamOrVarConst(i)) + stackAttributes[i] = ScriptableObject.CONST; + } + int definedArgs = idata.argCount; + if (definedArgs > argCount) { definedArgs = argCount; } + + // Fill the frame structure + + frame.parentFrame = parentFrame; + frame.frameIndex = (parentFrame == null) + ? 0 : parentFrame.frameIndex + 1; + if(frame.frameIndex > cx.getMaximumInterpreterStackDepth()) + { + throw Context.reportRuntimeError("Exceeded maximum stack depth"); + } + frame.frozen = false; + + frame.fnOrScript = fnOrScript; + frame.idata = idata; + + frame.stack = stack; + frame.stackAttributes = stackAttributes; + frame.sDbl = sDbl; + frame.varSource = frame; + frame.localShift = idata.itsMaxVars; + frame.emptyStackTop = emptyStackTop; + + frame.debuggerFrame = debuggerFrame; + frame.useActivation = useActivation; + + frame.thisObj = thisObj; + frame.scriptRegExps = scriptRegExps; + + // Initialize initial values of variables that change during + // interpretation. + frame.result = Undefined.instance; + frame.pc = 0; + frame.pcPrevBranch = 0; + frame.pcSourceLineStart = idata.firstLinePC; + frame.scope = scope; + + frame.savedStackTop = emptyStackTop; + frame.savedCallOp = 0; + + System.arraycopy(args, argShift, stack, 0, definedArgs); + if (argsDbl != null) { + System.arraycopy(argsDbl, argShift, sDbl, 0, definedArgs); + } + for (int i = definedArgs; i != idata.itsMaxVars; ++i) { + stack[i] = Undefined.instance; + } + if (stackReuse) { + // Clean the stack part and space beyond stack if any + // of the old array to allow to GC objects there + for (int i = emptyStackTop + 1; i != stack.length; ++i) { + stack[i] = null; + } + } + + enterFrame(cx, frame, args, false); + } + + private static boolean isFrameEnterExitRequired(CallFrame frame) + { + return frame.debuggerFrame != null || frame.idata.itsNeedsActivation; + } + + private static void enterFrame(Context cx, CallFrame frame, Object[] args, + boolean continuationRestart) + { + boolean usesActivation = frame.idata.itsNeedsActivation; + boolean isDebugged = frame.debuggerFrame != null; + if(usesActivation || isDebugged) { + Scriptable scope = frame.scope; + if(scope == null) { + Kit.codeBug(); + } else if (continuationRestart) { + // Walk the parent chain of frame.scope until a NativeCall is + // found. Normally, frame.scope is a NativeCall when called + // from initFrame() for a debugged or activatable function. + // However, when called from interpretLoop() as part of + // restarting a continuation, it can also be a NativeWith if + // the continuation was captured within a "with" or "catch" + // block ("catch" implicitly uses NativeWith to create a scope + // to expose the exception variable). + for(;;) { + if(scope instanceof NativeWith) { + scope = scope.getParentScope(); + if (scope == null || (frame.parentFrame != null && + frame.parentFrame.scope == scope)) + { + // If we get here, we didn't find a NativeCall in + // the call chain before reaching parent frame's + // scope. This should not be possible. + Kit.codeBug(); + break; // Never reached, but keeps the static analyzer happy about "scope" not being null 5 lines above. + } + } + else { + break; + } + } + } + if (isDebugged) { + frame.debuggerFrame.onEnter(cx, scope, frame.thisObj, args); + } + // Enter activation only when itsNeedsActivation true, + // since debugger should not interfere with activation + // chaining + if (usesActivation) { + ScriptRuntime.enterActivationFunction(cx, scope); + } + } + } + + private static void exitFrame(Context cx, CallFrame frame, + Object throwable) + { + if (frame.idata.itsNeedsActivation) { + ScriptRuntime.exitActivationFunction(cx); + } + + if (frame.debuggerFrame != null) { + try { + if (throwable instanceof Throwable) { + frame.debuggerFrame.onExit(cx, true, throwable); + } else { + Object result; + ContinuationJump cjump = (ContinuationJump)throwable; + if (cjump == null) { + result = frame.result; + } else { + result = cjump.result; + } + if (result == UniqueTag.DOUBLE_MARK) { + double resultDbl; + if (cjump == null) { + resultDbl = frame.resultDbl; + } else { + resultDbl = cjump.resultDbl; + } + result = ScriptRuntime.wrapNumber(resultDbl); + } + frame.debuggerFrame.onExit(cx, false, result); + } + } catch (Throwable ex) { + System.err.println( +"RHINO USAGE WARNING: onExit terminated with exception"); + ex.printStackTrace(System.err); + } + } + } + + private static void setCallResult(CallFrame frame, + Object callResult, + double callResultDbl) + { + if (frame.savedCallOp == Token.CALL) { + frame.stack[frame.savedStackTop] = callResult; + frame.sDbl[frame.savedStackTop] = callResultDbl; + } else if (frame.savedCallOp == Token.NEW) { + // If construct returns scriptable, + // then it replaces on stack top saved original instance + // of the object. + if (callResult instanceof Scriptable) { + frame.stack[frame.savedStackTop] = callResult; + } + } else { + Kit.codeBug(); + } + frame.savedCallOp = 0; + } + + private static void captureContinuation(Context cx, CallFrame frame, + int stackTop) + { + Continuation c = new Continuation(); + ScriptRuntime.setObjectProtoAndParent( + c, ScriptRuntime.getTopCallScope(cx)); + + // Make sure that all frames upstack frames are frozen + CallFrame x = frame.parentFrame; + while (x != null && !x.frozen) { + x.frozen = true; + // Allow to GC unused stack space + for (int i = x.savedStackTop + 1; i != x.stack.length; ++i) { + // Allow to GC unused stack space + x.stack[i] = null; + x.stackAttributes[i] = ScriptableObject.EMPTY; + } + if (x.savedCallOp == Token.CALL) { + // the call will always overwrite the stack top with the result + x.stack[x.savedStackTop] = null; + } else { + if (x.savedCallOp != Token.NEW) Kit.codeBug(); + // the new operator uses stack top to store the constructed + // object so it shall not be cleared: see comments in + // setCallResult + } + x = x.parentFrame; + } + + c.initImplementation(frame.parentFrame); + frame.stack[stackTop] = c; + } + + private static int stack_int32(CallFrame frame, int i) + { + Object x = frame.stack[i]; + double value; + if (x == UniqueTag.DOUBLE_MARK) { + value = frame.sDbl[i]; + } else { + value = ScriptRuntime.toNumber(x); + } + return ScriptRuntime.toInt32(value); + } + + private static double stack_double(CallFrame frame, int i) + { + Object x = frame.stack[i]; + if (x != UniqueTag.DOUBLE_MARK) { + return ScriptRuntime.toNumber(x); + } else { + return frame.sDbl[i]; + } + } + + private static boolean stack_boolean(CallFrame frame, int i) + { + Object x = frame.stack[i]; + if (x == Boolean.TRUE) { + return true; + } else if (x == Boolean.FALSE) { + return false; + } else if (x == UniqueTag.DOUBLE_MARK) { + double d = frame.sDbl[i]; + return d == d && d != 0.0; + } else if (x == null || x == Undefined.instance) { + return false; + } else if (x instanceof Number) { + double d = ((Number)x).doubleValue(); + return (d == d && d != 0.0); + } else if (x instanceof Boolean) { + return ((Boolean)x).booleanValue(); + } else { + return ScriptRuntime.toBoolean(x); + } + } + + private static void do_add(Object[] stack, double[] sDbl, int stackTop, + Context cx) + { + Object rhs = stack[stackTop + 1]; + Object lhs = stack[stackTop]; + double d; + boolean leftRightOrder; + if (rhs == UniqueTag.DOUBLE_MARK) { + d = sDbl[stackTop + 1]; + if (lhs == UniqueTag.DOUBLE_MARK) { + sDbl[stackTop] += d; + return; + } + leftRightOrder = true; + // fallthrough to object + number code + } else if (lhs == UniqueTag.DOUBLE_MARK) { + d = sDbl[stackTop]; + lhs = rhs; + leftRightOrder = false; + // fallthrough to object + number code + } else { + if (lhs instanceof Scriptable || rhs instanceof Scriptable) { + stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx); + } else if (lhs instanceof String) { + String lstr = (String)lhs; + String rstr = ScriptRuntime.toString(rhs); + stack[stackTop] = lstr.concat(rstr); + } else if (rhs instanceof String) { + String lstr = ScriptRuntime.toString(lhs); + String rstr = (String)rhs; + stack[stackTop] = lstr.concat(rstr); + } else { + double lDbl = (lhs instanceof Number) + ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs); + double rDbl = (rhs instanceof Number) + ? ((Number)rhs).doubleValue() : ScriptRuntime.toNumber(rhs); + stack[stackTop] = UniqueTag.DOUBLE_MARK; + sDbl[stackTop] = lDbl + rDbl; + } + return; + } + + // handle object(lhs) + number(d) code + if (lhs instanceof Scriptable) { + rhs = ScriptRuntime.wrapNumber(d); + if (!leftRightOrder) { + Object tmp = lhs; + lhs = rhs; + rhs = tmp; + } + stack[stackTop] = ScriptRuntime.add(lhs, rhs, cx); + } else if (lhs instanceof String) { + String lstr = (String)lhs; + String rstr = ScriptRuntime.toString(d); + if (leftRightOrder) { + stack[stackTop] = lstr.concat(rstr); + } else { + stack[stackTop] = rstr.concat(lstr); + } + } else { + double lDbl = (lhs instanceof Number) + ? ((Number)lhs).doubleValue() : ScriptRuntime.toNumber(lhs); + stack[stackTop] = UniqueTag.DOUBLE_MARK; + sDbl[stackTop] = lDbl + d; + } + } + + private static Object[] getArgsArray(Object[] stack, double[] sDbl, + int shift, int count) + { + if (count == 0) { + return ScriptRuntime.emptyArgs; + } + Object[] args = new Object[count]; + for (int i = 0; i != count; ++i, ++shift) { + Object val = stack[shift]; + if (val == UniqueTag.DOUBLE_MARK) { + val = ScriptRuntime.wrapNumber(sDbl[shift]); + } + args[i] = val; + } + return args; + } + + private static void addInstructionCount(Context cx, CallFrame frame, + int extra) + { + cx.instructionCount += frame.pc - frame.pcPrevBranch + extra; + if (cx.instructionCount > cx.instructionThreshold) { + cx.observeInstructionCount(cx.instructionCount); + cx.instructionCount = 0; + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpreterData.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpreterData.java new file mode 100644 index 0000000..7435b10 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/InterpreterData.java @@ -0,0 +1,192 @@ +/* -*- 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): + * Norris Boyd + * Bob Jervis + * Roger Lawrence + * + * 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.io.Serializable; + +import org.mozilla.javascript.debug.DebuggableScript; + +final class InterpreterData implements Serializable, DebuggableScript +{ + static final long serialVersionUID = 5067677351589230234L; + + static final int INITIAL_MAX_ICODE_LENGTH = 1024; + static final int INITIAL_STRINGTABLE_SIZE = 64; + static final int INITIAL_NUMBERTABLE_SIZE = 64; + + InterpreterData(int languageVersion, + String sourceFile, String encodedSource) + { + this.languageVersion = languageVersion; + this.itsSourceFile = sourceFile; + this.encodedSource = encodedSource; + + init(); + } + + InterpreterData(InterpreterData parent) + { + this.parentData = parent; + this.languageVersion = parent.languageVersion; + this.itsSourceFile = parent.itsSourceFile; + this.encodedSource = parent.encodedSource; + + init(); + } + + private void init() + { + itsICode = new byte[INITIAL_MAX_ICODE_LENGTH]; + itsStringTable = new String[INITIAL_STRINGTABLE_SIZE]; + } + + String itsName; + String itsSourceFile; + boolean itsNeedsActivation; + int itsFunctionType; + + String[] itsStringTable; + double[] itsDoubleTable; + InterpreterData[] itsNestedFunctions; + Object[] itsRegExpLiterals; + + byte[] itsICode; + + int[] itsExceptionTable; + + int itsMaxVars; + int itsMaxLocals; + int itsMaxStack; + int itsMaxFrameArray; + + // see comments in NativeFuncion for definition of argNames and argCount + String[] argNames; + boolean[] argIsConst; + int argCount; + + int itsMaxCalleeArgs; + + String encodedSource; + int encodedSourceStart; + int encodedSourceEnd; + + int languageVersion; + + boolean useDynamicScope; + + boolean topLevel; + + Object[] literalIds; + + UintMap longJumps; + + int firstLinePC = -1; // PC for the first LINE icode + + InterpreterData parentData; + + boolean evalScriptFlag; // true if script corresponds to eval() code + + public boolean isTopLevel() + { + return topLevel; + } + + public boolean isFunction() + { + return itsFunctionType != 0; + } + + public String getFunctionName() + { + return itsName; + } + + public int getParamCount() + { + return argCount; + } + + public int getParamAndVarCount() + { + return argNames.length; + } + + public String getParamOrVarName(int index) + { + return argNames[index]; + } + + public boolean getParamOrVarConst(int index) + { + return argIsConst[index]; + } + + public String getSourceName() + { + return itsSourceFile; + } + + public boolean isGeneratedScript() + { + return ScriptRuntime.isGeneratedScript(itsSourceFile); + } + + public int[] getLineNumbers() + { + return Interpreter.getLineNumbers(this); + } + + public int getFunctionCount() + { + return (itsNestedFunctions == null) ? 0 : itsNestedFunctions.length; + } + + public DebuggableScript getFunction(int index) + { + return itsNestedFunctions[index]; + } + + public DebuggableScript getParent() + { + return parentData; + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java new file mode 100644 index 0000000..6e0a827 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaAdapter.java @@ -0,0 +1,1129 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Patrick Beard + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * Matthias Radestock + * Andi Vajda + * Andrew Wason + * Kemal Bayram + * + * 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 org.mozilla.classfile.*; +import java.lang.reflect.*; +import java.io.*; +import java.security.*; +import java.util.*; + +public final class JavaAdapter implements IdFunctionCall +{ + /** + * Provides a key with which to distinguish previously generated + * adapter classes stored in a hash table. + */ + static class JavaAdapterSignature + { + Class superClass; + Class[] interfaces; + ObjToIntMap names; + + JavaAdapterSignature(Class superClass, Class[] interfaces, + ObjToIntMap names) + { + this.superClass = superClass; + this.interfaces = interfaces; + this.names = names; + } + + public boolean equals(Object obj) + { + if (!(obj instanceof JavaAdapterSignature)) + return false; + JavaAdapterSignature sig = (JavaAdapterSignature) obj; + if (superClass != sig.superClass) + return false; + if (interfaces != sig.interfaces) { + if (interfaces.length != sig.interfaces.length) + return false; + for (int i=0; i < interfaces.length; i++) + if (interfaces[i] != sig.interfaces[i]) + return false; + } + if (names.size() != sig.names.size()) + return false; + ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(names); + for (iter.start(); !iter.done(); iter.next()) { + String name = (String)iter.getKey(); + int arity = iter.getValue(); + if (arity != names.get(name, arity + 1)) + return false; + } + return true; + } + + public int hashCode() + { + return superClass.hashCode() + | (0x9e3779b9 * (names.size() | (interfaces.length << 16))); + } + } + + public static void init(Context cx, Scriptable scope, boolean sealed) + { + JavaAdapter obj = new JavaAdapter(); + IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_JavaAdapter, + "JavaAdapter", 1, scope); + ctor.markAsConstructor(null); + if (sealed) { + ctor.sealObject(); + } + ctor.exportAsScopeProperty(); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (f.hasTag(FTAG)) { + if (f.methodId() == Id_JavaAdapter) { + return js_createAdapter(cx, scope, args); + } + } + throw f.unknown(); + } + + public static Object convertResult(Object result, Class c) + { + if (result == Undefined.instance && + (c != ScriptRuntime.ObjectClass && + c != ScriptRuntime.StringClass)) + { + // Avoid an error for an undefined value; return null instead. + return null; + } + return Context.jsToJava(result, c); + } + + public static Scriptable createAdapterWrapper(Scriptable obj, + Object adapter) + { + Scriptable scope = ScriptableObject.getTopLevelScope(obj); + NativeJavaObject res = new NativeJavaObject(scope, adapter, null, true); + res.setPrototype(obj); + return res; + } + + public static Object getAdapterSelf(Class adapterClass, Object adapter) + throws NoSuchFieldException, IllegalAccessException + { + Field self = adapterClass.getDeclaredField("self"); + return self.get(adapter); + } + + static Object js_createAdapter(Context cx, Scriptable scope, Object[] args) + { + int N = args.length; + if (N == 0) { + throw ScriptRuntime.typeError0("msg.adapter.zero.args"); + } + + Class superClass = null; + Class[] intfs = new Class[N - 1]; + int interfaceCount = 0; + for (int i = 0; i != N - 1; ++i) { + Object arg = args[i]; + if (!(arg instanceof NativeJavaClass)) { + throw ScriptRuntime.typeError2("msg.not.java.class.arg", + String.valueOf(i), + ScriptRuntime.toString(arg)); + } + Class c = ((NativeJavaClass) arg).getClassObject(); + if (!c.isInterface()) { + if (superClass != null) { + throw ScriptRuntime.typeError2("msg.only.one.super", + superClass.getName(), c.getName()); + } + superClass = c; + } else { + intfs[interfaceCount++] = c; + } + } + + if (superClass == null) + superClass = ScriptRuntime.ObjectClass; + + Class[] interfaces = new Class[interfaceCount]; + System.arraycopy(intfs, 0, interfaces, 0, interfaceCount); + Scriptable obj = ScriptRuntime.toObject(cx, scope, args[N - 1]); + + Class adapterClass = getAdapterClass(scope, superClass, interfaces, + obj); + + Class[] ctorParms = { + ScriptRuntime.ContextFactoryClass, + ScriptRuntime.ScriptableClass + }; + Object[] ctorArgs = { cx.getFactory(), obj }; + try { + Object adapter = adapterClass.getConstructor(ctorParms). + newInstance(ctorArgs); + return getAdapterSelf(adapterClass, adapter); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + + // Needed by NativeJavaObject serializer + public static void writeAdapterObject(Object javaObject, + ObjectOutputStream out) + throws IOException + { + Class cl = javaObject.getClass(); + out.writeObject(cl.getSuperclass().getName()); + + Class[] interfaces = cl.getInterfaces(); + String[] interfaceNames = new String[interfaces.length]; + + for (int i=0; i < interfaces.length; i++) + interfaceNames[i] = interfaces[i].getName(); + + out.writeObject(interfaceNames); + + try { + Object delegee = cl.getField("delegee").get(javaObject); + out.writeObject(delegee); + return; + } catch (IllegalAccessException e) { + } catch (NoSuchFieldException e) { + } + throw new IOException(); + } + + // Needed by NativeJavaObject de-serializer + public static Object readAdapterObject(Scriptable self, + ObjectInputStream in) + throws IOException, ClassNotFoundException + { + ContextFactory factory; + Context cx = Context.getCurrentContext(); + if (cx != null) { + factory = cx.getFactory(); + } else { + factory = null; + } + + Class superClass = Class.forName((String)in.readObject()); + + String[] interfaceNames = (String[])in.readObject(); + Class[] interfaces = new Class[interfaceNames.length]; + + for (int i=0; i < interfaceNames.length; i++) + interfaces[i] = Class.forName(interfaceNames[i]); + + Scriptable delegee = (Scriptable)in.readObject(); + + Class adapterClass = getAdapterClass(self, superClass, interfaces, + delegee); + + Class[] ctorParms = { + ScriptRuntime.ContextFactoryClass, + ScriptRuntime.ScriptableClass, + ScriptRuntime.ScriptableClass + }; + Object[] ctorArgs = { factory, delegee, self }; + try { + return adapterClass.getConstructor(ctorParms).newInstance(ctorArgs); + } catch(InstantiationException e) { + } catch(IllegalAccessException e) { + } catch(InvocationTargetException e) { + } catch(NoSuchMethodException e) { + } + + throw new ClassNotFoundException("adapter"); + } + + private static ObjToIntMap getObjectFunctionNames(Scriptable obj) + { + Object[] ids = ScriptableObject.getPropertyIds(obj); + ObjToIntMap map = new ObjToIntMap(ids.length); + for (int i = 0; i != ids.length; ++i) { + if (!(ids[i] instanceof String)) + continue; + String id = (String) ids[i]; + Object value = ScriptableObject.getProperty(obj, id); + if (value instanceof Function) { + Function f = (Function)value; + int length = ScriptRuntime.toInt32( + ScriptableObject.getProperty(f, "length")); + if (length < 0) { + length = 0; + } + map.put(id, length); + } + } + return map; + } + + private static Class getAdapterClass(Scriptable scope, Class superClass, + Class[] interfaces, Scriptable obj) + { + ClassCache cache = ClassCache.get(scope); + Map<JavaAdapterSignature,Class<?>> generated + = cache.getInterfaceAdapterCacheMap(); + + ObjToIntMap names = getObjectFunctionNames(obj); + JavaAdapterSignature sig; + sig = new JavaAdapterSignature(superClass, interfaces, names); + Class<?> adapterClass = generated.get(sig); + if (adapterClass == null) { + String adapterName = "adapter" + + cache.newClassSerialNumber(); + byte[] code = createAdapterCode(names, adapterName, + superClass, interfaces, null); + + adapterClass = loadAdapterClass(adapterName, code); + if (cache.isCachingEnabled()) { + generated.put(sig, adapterClass); + } + } + return adapterClass; + } + + public static byte[] createAdapterCode(ObjToIntMap functionNames, + String adapterName, + Class superClass, + Class[] interfaces, + String scriptClassName) + { + ClassFileWriter cfw = new ClassFileWriter(adapterName, + superClass.getName(), + "<adapter>"); + cfw.addField("factory", "Lorg/mozilla/javascript/ContextFactory;", + (short) (ClassFileWriter.ACC_PUBLIC | + ClassFileWriter.ACC_FINAL)); + cfw.addField("delegee", "Lorg/mozilla/javascript/Scriptable;", + (short) (ClassFileWriter.ACC_PUBLIC | + ClassFileWriter.ACC_FINAL)); + cfw.addField("self", "Lorg/mozilla/javascript/Scriptable;", + (short) (ClassFileWriter.ACC_PUBLIC | + ClassFileWriter.ACC_FINAL)); + int interfacesCount = interfaces == null ? 0 : interfaces.length; + for (int i=0; i < interfacesCount; i++) { + if (interfaces[i] != null) + cfw.addInterface(interfaces[i].getName()); + } + + String superName = superClass.getName().replace('.', '/'); + generateCtor(cfw, adapterName, superName); + generateSerialCtor(cfw, adapterName, superName); + if (scriptClassName != null) + generateEmptyCtor(cfw, adapterName, superName, scriptClassName); + + ObjToIntMap generatedOverrides = new ObjToIntMap(); + ObjToIntMap generatedMethods = new ObjToIntMap(); + + // generate methods to satisfy all specified interfaces. + for (int i = 0; i < interfacesCount; i++) { + Method[] methods = interfaces[i].getMethods(); + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + int mods = method.getModifiers(); + if (Modifier.isStatic(mods) || Modifier.isFinal(mods)) { + continue; + } + String methodName = method.getName(); + Class[] argTypes = method.getParameterTypes(); + if (!functionNames.has(methodName)) { + try { + superClass.getMethod(methodName, argTypes); + // The class we're extending implements this method and + // the JavaScript object doesn't have an override. See + // bug 61226. + continue; + } catch (NoSuchMethodException e) { + // Not implemented by superclass; fall through + } + } + // make sure to generate only one instance of a particular + // method/signature. + String methodSignature = getMethodSignature(method, argTypes); + String methodKey = methodName + methodSignature; + if (! generatedOverrides.has(methodKey)) { + generateMethod(cfw, adapterName, methodName, + argTypes, method.getReturnType()); + generatedOverrides.put(methodKey, 0); + generatedMethods.put(methodName, 0); + } + } + } + + // Now, go through the superclass's methods, checking for abstract + // methods or additional methods to override. + + // generate any additional overrides that the object might contain. + Method[] methods = getOverridableMethods(superClass); + for (int j = 0; j < methods.length; j++) { + Method method = methods[j]; + int mods = method.getModifiers(); + // if a method is marked abstract, must implement it or the + // resulting class won't be instantiable. otherwise, if the object + // has a property of the same name, then an override is intended. + boolean isAbstractMethod = Modifier.isAbstract(mods); + String methodName = method.getName(); + if (isAbstractMethod || functionNames.has(methodName)) { + // make sure to generate only one instance of a particular + // method/signature. + Class[] argTypes = method.getParameterTypes(); + String methodSignature = getMethodSignature(method, argTypes); + String methodKey = methodName + methodSignature; + if (! generatedOverrides.has(methodKey)) { + generateMethod(cfw, adapterName, methodName, + argTypes, method.getReturnType()); + generatedOverrides.put(methodKey, 0); + generatedMethods.put(methodName, 0); + + // if a method was overridden, generate a "super$method" + // which lets the delegate call the superclass' version. + if (!isAbstractMethod) { + generateSuper(cfw, adapterName, superName, + methodName, methodSignature, + argTypes, method.getReturnType()); + } + } + } + } + + // Generate Java methods for remaining properties that are not + // overrides. + ObjToIntMap.Iterator iter = new ObjToIntMap.Iterator(functionNames); + for (iter.start(); !iter.done(); iter.next()) { + String functionName = (String)iter.getKey(); + if (generatedMethods.has(functionName)) + continue; + int length = iter.getValue(); + Class[] parms = new Class[length]; + for (int k=0; k < length; k++) + parms[k] = ScriptRuntime.ObjectClass; + generateMethod(cfw, adapterName, functionName, parms, + ScriptRuntime.ObjectClass); + } + return cfw.toByteArray(); + } + + static Method[] getOverridableMethods(Class c) + { + ArrayList<Method> list = new ArrayList<Method>(); + HashSet<String> skip = new HashSet<String>(); + while (c != null) { + Method[] methods = c.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + String methodKey = methods[i].getName() + + getMethodSignature(methods[i], + methods[i].getParameterTypes()); + if (skip.contains(methodKey)) + continue; // skip this method + int mods = methods[i].getModifiers(); + if (Modifier.isStatic(mods)) + continue; + if (Modifier.isFinal(mods)) { + // Make sure we don't add a final method to the list + // of overridable methods. + skip.add(methodKey); + continue; + } + if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) { + list.add(methods[i]); + skip.add(methodKey); + } + } + c = c.getSuperclass(); + } + return list.toArray(new Method[list.size()]); + } + + static Class loadAdapterClass(String className, byte[] classBytes) + { + Object staticDomain; + Class domainClass = SecurityController.getStaticSecurityDomainClass(); + if(domainClass == CodeSource.class || domainClass == ProtectionDomain.class) { + ProtectionDomain protectionDomain = JavaAdapter.class.getProtectionDomain(); + if(domainClass == CodeSource.class) { + staticDomain = protectionDomain == null ? null : protectionDomain.getCodeSource(); + } + else { + staticDomain = protectionDomain; + } + } + else { + staticDomain = null; + } + GeneratedClassLoader loader = SecurityController.createLoader(null, + staticDomain); + Class result = loader.defineClass(className, classBytes); + loader.linkClass(result); + return result; + } + + public static Function getFunction(Scriptable obj, String functionName) + { + Object x = ScriptableObject.getProperty(obj, functionName); + if (x == Scriptable.NOT_FOUND) { + // This method used to swallow the exception from calling + // an undefined method. People have come to depend on this + // somewhat dubious behavior. It allows people to avoid + // implementing listener methods that they don't care about, + // for instance. + return null; + } + if (!(x instanceof Function)) + throw ScriptRuntime.notFunctionError(x, functionName); + + return (Function)x; + } + + /** + * Utility method which dynamically binds a Context to the current thread, + * if none already exists. + */ + public static Object callMethod(ContextFactory factory, + final Scriptable thisObj, + final Function f, final Object[] args, + final long argsToWrap) + { + if (f == null) { + // See comments in getFunction + return Undefined.instance; + } + if (factory == null) { + factory = ContextFactory.getGlobal(); + } + + final Scriptable scope = f.getParentScope(); + if (argsToWrap == 0) { + return Context.call(factory, f, scope, thisObj, args); + } + + Context cx = Context.getCurrentContext(); + if (cx != null) { + return doCall(cx, scope, thisObj, f, args, argsToWrap); + } else { + return factory.call(new ContextAction() { + public Object run(Context cx) + { + return doCall(cx, scope, thisObj, f, args, argsToWrap); + } + }); + } + } + + private static Object doCall(Context cx, Scriptable scope, + Scriptable thisObj, Function f, + Object[] args, long argsToWrap) + { + // Wrap the rest of objects + for (int i = 0; i != args.length; ++i) { + if (0 != (argsToWrap & (1 << i))) { + Object arg = args[i]; + if (!(arg instanceof Scriptable)) { + args[i] = cx.getWrapFactory().wrap(cx, scope, arg, + null); + } + } + } + return f.call(cx, scope, thisObj, args); + } + + public static Scriptable runScript(final Script script) + { + return (Scriptable)Context.call(new ContextAction() { + public Object run(Context cx) + { + ScriptableObject global = ScriptRuntime.getGlobal(cx); + script.exec(cx, global); + return global; + } + }); + } + + private static void generateCtor(ClassFileWriter cfw, String adapterName, + String superName) + { + cfw.startMethod("<init>", + "(Lorg/mozilla/javascript/ContextFactory;" + +"Lorg/mozilla/javascript/Scriptable;)V", + ClassFileWriter.ACC_PUBLIC); + + // Invoke base class constructor + cfw.add(ByteCode.ALOAD_0); // this + cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); + + // Save parameter in instance variable "factory" + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance + cfw.add(ByteCode.PUTFIELD, adapterName, "factory", + "Lorg/mozilla/javascript/ContextFactory;"); + + // Save parameter in instance variable "delegee" + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee + cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", + "Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self + // create a wrapper object to be used as "this" in method calls + cfw.add(ByteCode.ALOAD_2); // the Scriptable delegee + cfw.add(ByteCode.ALOAD_0); // this + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "createAdapterWrapper", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.add(ByteCode.PUTFIELD, adapterName, "self", + "Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)3); // 3: this + factory + delegee + } + + private static void generateSerialCtor(ClassFileWriter cfw, + String adapterName, + String superName) + { + cfw.startMethod("<init>", + "(Lorg/mozilla/javascript/ContextFactory;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V", + ClassFileWriter.ACC_PUBLIC); + + // Invoke base class constructor + cfw.add(ByteCode.ALOAD_0); // this + cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); + + // Save parameter in instance variable "factory" + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance + cfw.add(ByteCode.PUTFIELD, adapterName, "factory", + "Lorg/mozilla/javascript/ContextFactory;"); + + // Save parameter in instance variable "delegee" + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee + cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", + "Lorg/mozilla/javascript/Scriptable;"); + // save self + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_3); // second arg: Scriptable self + cfw.add(ByteCode.PUTFIELD, adapterName, "self", + "Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)4); // 4: this + factory + delegee + self + } + + private static void generateEmptyCtor(ClassFileWriter cfw, + String adapterName, + String superName, + String scriptClassName) + { + cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC); + + // Invoke base class constructor + cfw.add(ByteCode.ALOAD_0); // this + cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V"); + + // Set factory to null to use current global when necessary + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.ACONST_NULL); + cfw.add(ByteCode.PUTFIELD, adapterName, "factory", + "Lorg/mozilla/javascript/ContextFactory;"); + + // Load script class + cfw.add(ByteCode.NEW, scriptClassName); + cfw.add(ByteCode.DUP); + cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V"); + + // Run script and save resulting scope + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "runScript", + "(Lorg/mozilla/javascript/Script;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.add(ByteCode.ASTORE_1); + + // Save the Scriptable in instance variable "delegee" + cfw.add(ByteCode.ALOAD_0); // this + cfw.add(ByteCode.ALOAD_1); // the Scriptable + cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", + "Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self + // create a wrapper object to be used as "this" in method calls + cfw.add(ByteCode.ALOAD_1); // the Scriptable + cfw.add(ByteCode.ALOAD_0); // this + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "createAdapterWrapper", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.add(ByteCode.PUTFIELD, adapterName, "self", + "Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)2); // this + delegee + } + + /** + * Generates code to wrap Java arguments into Object[]. + * Non-primitive Java types are left as-is pending conversion + * in the helper method. Leaves the array object on the top of the stack. + */ + static void generatePushWrappedArgs(ClassFileWriter cfw, + Class[] argTypes, + int arrayLength) + { + // push arguments + cfw.addPush(arrayLength); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + int paramOffset = 1; + for (int i = 0; i != argTypes.length; ++i) { + cfw.add(ByteCode.DUP); // duplicate array reference + cfw.addPush(i); + paramOffset += generateWrapArg(cfw, paramOffset, argTypes[i]); + cfw.add(ByteCode.AASTORE); + } + } + + /** + * Generates code to wrap Java argument into Object. + * Non-primitive Java types are left unconverted pending conversion + * in the helper method. Leaves the wrapper object on the top of the stack. + */ + private static int generateWrapArg(ClassFileWriter cfw, int paramOffset, + Class argType) + { + int size = 1; + if (!argType.isPrimitive()) { + cfw.add(ByteCode.ALOAD, paramOffset); + + } else if (argType == Boolean.TYPE) { + // wrap boolean values with java.lang.Boolean. + cfw.add(ByteCode.NEW, "java/lang/Boolean"); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.ILOAD, paramOffset); + cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean", + "<init>", "(Z)V"); + + } else if (argType == Character.TYPE) { + // Create a string of length 1 using the character parameter. + cfw.add(ByteCode.ILOAD, paramOffset); + cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String", + "valueOf", "(C)Ljava/lang/String;"); + + } else { + // convert all numeric values to java.lang.Double. + cfw.add(ByteCode.NEW, "java/lang/Double"); + cfw.add(ByteCode.DUP); + String typeName = argType.getName(); + switch (typeName.charAt(0)) { + case 'b': + case 's': + case 'i': + // load an int value, convert to double. + cfw.add(ByteCode.ILOAD, paramOffset); + cfw.add(ByteCode.I2D); + break; + case 'l': + // load a long, convert to double. + cfw.add(ByteCode.LLOAD, paramOffset); + cfw.add(ByteCode.L2D); + size = 2; + break; + case 'f': + // load a float, convert to double. + cfw.add(ByteCode.FLOAD, paramOffset); + cfw.add(ByteCode.F2D); + break; + case 'd': + cfw.add(ByteCode.DLOAD, paramOffset); + size = 2; + break; + } + cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double", + "<init>", "(D)V"); + } + return size; + } + + /** + * Generates code to convert a wrapped value type to a primitive type. + * Handles unwrapping java.lang.Boolean, and java.lang.Number types. + * Generates the appropriate RETURN bytecode. + */ + static void generateReturnResult(ClassFileWriter cfw, Class retType, + boolean callConvertResult) + { + // wrap boolean values with java.lang.Boolean, convert all other + // primitive values to java.lang.Double. + if (retType == Void.TYPE) { + cfw.add(ByteCode.POP); + cfw.add(ByteCode.RETURN); + + } else if (retType == Boolean.TYPE) { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/Context", + "toBoolean", "(Ljava/lang/Object;)Z"); + cfw.add(ByteCode.IRETURN); + + } else if (retType == Character.TYPE) { + // characters are represented as strings in JavaScript. + // return the first character. + // first convert the value to a string if possible. + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/Context", + "toString", + "(Ljava/lang/Object;)Ljava/lang/String;"); + cfw.add(ByteCode.ICONST_0); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String", + "charAt", "(I)C"); + cfw.add(ByteCode.IRETURN); + + } else if (retType.isPrimitive()) { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/Context", + "toNumber", "(Ljava/lang/Object;)D"); + String typeName = retType.getName(); + switch (typeName.charAt(0)) { + case 'b': + case 's': + case 'i': + cfw.add(ByteCode.D2I); + cfw.add(ByteCode.IRETURN); + break; + case 'l': + cfw.add(ByteCode.D2L); + cfw.add(ByteCode.LRETURN); + break; + case 'f': + cfw.add(ByteCode.D2F); + cfw.add(ByteCode.FRETURN); + break; + case 'd': + cfw.add(ByteCode.DRETURN); + break; + default: + throw new RuntimeException("Unexpected return type " + + retType.toString()); + } + + } else { + String retTypeStr = retType.getName(); + if (callConvertResult) { + cfw.addLoadConstant(retTypeStr); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "java/lang/Class", + "forName", + "(Ljava/lang/String;)Ljava/lang/Class;"); + + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "convertResult", + "(Ljava/lang/Object;" + +"Ljava/lang/Class;" + +")Ljava/lang/Object;"); + } + // Now cast to return type + cfw.add(ByteCode.CHECKCAST, retTypeStr); + cfw.add(ByteCode.ARETURN); + } + } + + private static void generateMethod(ClassFileWriter cfw, String genName, + String methodName, Class[] parms, + Class returnType) + { + StringBuffer sb = new StringBuffer(); + int paramsEnd = appendMethodSignature(parms, returnType, sb); + String methodSignature = sb.toString(); + cfw.startMethod(methodName, methodSignature, + ClassFileWriter.ACC_PUBLIC); + + // Prepare stack to call method + + // push factory + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, genName, "factory", + "Lorg/mozilla/javascript/ContextFactory;"); + + // push self + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, genName, "self", + "Lorg/mozilla/javascript/Scriptable;"); + + // push function + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, genName, "delegee", + "Lorg/mozilla/javascript/Scriptable;"); + cfw.addPush(methodName); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "getFunction", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Lorg/mozilla/javascript/Function;"); + + // push arguments + generatePushWrappedArgs(cfw, parms, parms.length); + + // push bits to indicate which parameters should be wrapped + if (parms.length > 64) { + // If it will be an issue, then passing a static boolean array + // can be an option, but for now using simple bitmask + throw Context.reportRuntimeError0( + "JavaAdapter can not subclass methods with more then" + +" 64 arguments."); + } + long convertionMask = 0; + for (int i = 0; i != parms.length; ++i) { + if (!parms[i].isPrimitive()) { + convertionMask |= (1 << i); + } + } + cfw.addPush(convertionMask); + + // go through utility method, which creates a Context to run the + // method in. + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/JavaAdapter", + "callMethod", + "(Lorg/mozilla/javascript/ContextFactory;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Function;" + +"[Ljava/lang/Object;" + +"J" + +")Ljava/lang/Object;"); + + generateReturnResult(cfw, returnType, true); + + cfw.stopMethod((short)paramsEnd); + } + + /** + * Generates code to push typed parameters onto the operand stack + * prior to a direct Java method call. + */ + private static int generatePushParam(ClassFileWriter cfw, int paramOffset, + Class paramType) + { + if (!paramType.isPrimitive()) { + cfw.addALoad(paramOffset); + return 1; + } + String typeName = paramType.getName(); + switch (typeName.charAt(0)) { + case 'z': + case 'b': + case 'c': + case 's': + case 'i': + // load an int value, convert to double. + cfw.addILoad(paramOffset); + return 1; + case 'l': + // load a long, convert to double. + cfw.addLLoad(paramOffset); + return 2; + case 'f': + // load a float, convert to double. + cfw.addFLoad(paramOffset); + return 1; + case 'd': + cfw.addDLoad(paramOffset); + return 2; + } + throw Kit.codeBug(); + } + + /** + * Generates code to return a Java type, after calling a Java method + * that returns the same type. + * Generates the appropriate RETURN bytecode. + */ + private static void generatePopResult(ClassFileWriter cfw, + Class retType) + { + if (retType.isPrimitive()) { + String typeName = retType.getName(); + switch (typeName.charAt(0)) { + case 'b': + case 'c': + case 's': + case 'i': + case 'z': + cfw.add(ByteCode.IRETURN); + break; + case 'l': + cfw.add(ByteCode.LRETURN); + break; + case 'f': + cfw.add(ByteCode.FRETURN); + break; + case 'd': + cfw.add(ByteCode.DRETURN); + break; + } + } else { + cfw.add(ByteCode.ARETURN); + } + } + + /** + * Generates a method called "super$methodName()" which can be called + * from JavaScript that is equivalent to calling "super.methodName()" + * from Java. Eventually, this may be supported directly in JavaScript. + */ + private static void generateSuper(ClassFileWriter cfw, + String genName, String superName, + String methodName, String methodSignature, + Class[] parms, Class returnType) + { + cfw.startMethod("super$" + methodName, methodSignature, + ClassFileWriter.ACC_PUBLIC); + + // push "this" + cfw.add(ByteCode.ALOAD, 0); + + // push the rest of the parameters. + int paramOffset = 1; + for (int i = 0; i < parms.length; i++) { + paramOffset += generatePushParam(cfw, paramOffset, parms[i]); + } + + // call the superclass implementation of the method. + cfw.addInvoke(ByteCode.INVOKESPECIAL, + superName, + methodName, + methodSignature); + + // now, handle the return type appropriately. + Class retType = returnType; + if (!retType.equals(Void.TYPE)) { + generatePopResult(cfw, retType); + } else { + cfw.add(ByteCode.RETURN); + } + cfw.stopMethod((short)(paramOffset + 1)); + } + + /** + * Returns a fully qualified method name concatenated with its signature. + */ + private static String getMethodSignature(Method method, Class[] argTypes) + { + StringBuffer sb = new StringBuffer(); + appendMethodSignature(argTypes, method.getReturnType(), sb); + return sb.toString(); + } + + static int appendMethodSignature(Class[] argTypes, + Class returnType, + StringBuffer sb) + { + sb.append('('); + int firstLocal = 1 + argTypes.length; // includes this. + for (int i = 0; i < argTypes.length; i++) { + Class type = argTypes[i]; + appendTypeString(sb, type); + if (type == Long.TYPE || type == Double.TYPE) { + // adjust for duble slot + ++firstLocal; + } + } + sb.append(')'); + appendTypeString(sb, returnType); + return firstLocal; + } + + private static StringBuffer appendTypeString(StringBuffer sb, Class type) + { + while (type.isArray()) { + sb.append('['); + type = type.getComponentType(); + } + if (type.isPrimitive()) { + char typeLetter; + if (type == Boolean.TYPE) { + typeLetter = 'Z'; + } else if (type == Long.TYPE) { + typeLetter = 'J'; + } else { + String typeName = type.getName(); + typeLetter = Character.toUpperCase(typeName.charAt(0)); + } + sb.append(typeLetter); + } else { + sb.append('L'); + sb.append(type.getName().replace('.', '/')); + sb.append(';'); + } + return sb; + } + + static int[] getArgsToConvert(Class[] argTypes) + { + int count = 0; + for (int i = 0; i != argTypes.length; ++i) { + if (!argTypes[i].isPrimitive()) + ++count; + } + if (count == 0) + return null; + int[] array = new int[count]; + count = 0; + for (int i = 0; i != argTypes.length; ++i) { + if (!argTypes[i].isPrimitive()) + array[count++] = i; + } + return array; + } + + private static final Object FTAG = new Object(); + private static final int Id_JavaAdapter = 1; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaMembers.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaMembers.java new file mode 100644 index 0000000..84ef2d4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaMembers.java @@ -0,0 +1,935 @@ +/* -*- 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): + * Norris Boyd + * Cameron McCormack + * Frank Mitchell + * Mike Shaver + * Kurt Westerfeld + * + * 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.util.*; + +/** + * + * @author Mike Shaver + * @author Norris Boyd + * @see NativeJavaObject + * @see NativeJavaClass + */ +class JavaMembers +{ + JavaMembers(Scriptable scope, Class cl) + { + this(scope, cl, false); + } + + JavaMembers(Scriptable scope, Class cl, boolean includeProtected) + { + try { + Context cx = ContextFactory.getGlobal().enterContext(); + ClassShutter shutter = cx.getClassShutter(); + if (shutter != null && !shutter.visibleToScripts(cl.getName())) { + throw Context.reportRuntimeError1("msg.access.prohibited", + cl.getName()); + } + this.includePrivate = cx.hasFeature( + Context.FEATURE_ENHANCED_JAVA_ACCESS); + this.members = new Hashtable(23); + this.staticMembers = new Hashtable(7); + this.cl = cl; + reflect(scope, includeProtected); + } finally { + Context.exit(); + } + } + + boolean has(String name, boolean isStatic) + { + Hashtable ht = isStatic ? staticMembers : members; + Object obj = ht.get(name); + if (obj != null) { + return true; + } + return findExplicitFunction(name, isStatic) != null; + } + + Object get(Scriptable scope, String name, Object javaObject, + boolean isStatic) + { + Hashtable ht = isStatic ? staticMembers : members; + Object member = ht.get(name); + if (!isStatic && member == null) { + // Try to get static member from instance (LC3) + member = staticMembers.get(name); + } + if (member == null) { + member = this.getExplicitFunction(scope, name, + javaObject, isStatic); + if (member == null) + return Scriptable.NOT_FOUND; + } + if (member instanceof Scriptable) { + return member; + } + Context cx = Context.getContext(); + Object rval; + Class type; + try { + if (member instanceof BeanProperty) { + BeanProperty bp = (BeanProperty) member; + if (bp.getter == null) + return Scriptable.NOT_FOUND; + rval = bp.getter.invoke(javaObject, Context.emptyArgs); + type = bp.getter.method().getReturnType(); + } else { + Field field = (Field) member; + rval = field.get(isStatic ? null : javaObject); + type = field.getType(); + } + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + // Need to wrap the object before we return it. + scope = ScriptableObject.getTopLevelScope(scope); + return cx.getWrapFactory().wrap(cx, scope, rval, type); + } + + void put(Scriptable scope, String name, Object javaObject, + Object value, boolean isStatic) + { + Hashtable ht = isStatic ? staticMembers : members; + Object member = ht.get(name); + if (!isStatic && member == null) { + // Try to get static member from instance (LC3) + member = staticMembers.get(name); + } + if (member == null) + throw reportMemberNotFound(name); + if (member instanceof FieldAndMethods) { + FieldAndMethods fam = (FieldAndMethods) ht.get(name); + member = fam.field; + } + + // Is this a bean property "set"? + if (member instanceof BeanProperty) { + BeanProperty bp = (BeanProperty)member; + if (bp.setter == null) { + throw reportMemberNotFound(name); + } + // If there's only one setter or if the value is null, use the + // main setter. Otherwise, let the NativeJavaMethod decide which + // setter to use: + if (bp.setters == null || value == null) { + Class setType = bp.setter.argTypes[0]; + Object[] args = { Context.jsToJava(value, setType) }; + try { + bp.setter.invoke(javaObject, args); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } else { + Object[] args = { value }; + bp.setters.call(Context.getContext(), + ScriptableObject.getTopLevelScope(scope), + scope, args); + } + } + else { + if (!(member instanceof Field)) { + String str = (member == null) ? "msg.java.internal.private" + : "msg.java.method.assign"; + throw Context.reportRuntimeError1(str, name); + } + Field field = (Field)member; + Object javaValue = Context.jsToJava(value, field.getType()); + try { + field.set(javaObject, javaValue); + } catch (IllegalAccessException accessEx) { + if ((field.getModifiers() & Modifier.FINAL) != 0) { + // treat Java final the same as JavaScript [[READONLY]] + return; + } + throw Context.throwAsScriptRuntimeEx(accessEx); + } catch (IllegalArgumentException argEx) { + throw Context.reportRuntimeError3( + "msg.java.internal.field.type", + value.getClass().getName(), field, + javaObject.getClass().getName()); + } + } + } + + Object[] getIds(boolean isStatic) + { + Hashtable ht = isStatic ? staticMembers : members; + int len = ht.size(); + Object[] result = new Object[len]; + Enumeration keys = ht.keys(); + for (int i=0; i < len; i++) + result[i] = keys.nextElement(); + return result; + } + + static String javaSignature(Class type) + { + if (!type.isArray()) { + return type.getName(); + } else { + int arrayDimension = 0; + do { + ++arrayDimension; + type = type.getComponentType(); + } while (type.isArray()); + String name = type.getName(); + String suffix = "[]"; + if (arrayDimension == 1) { + return name.concat(suffix); + } else { + int length = name.length() + arrayDimension * suffix.length(); + StringBuffer sb = new StringBuffer(length); + sb.append(name); + while (arrayDimension != 0) { + --arrayDimension; + sb.append(suffix); + } + return sb.toString(); + } + } + } + + static String liveConnectSignature(Class[] argTypes) + { + int N = argTypes.length; + if (N == 0) { return "()"; } + StringBuffer sb = new StringBuffer(); + sb.append('('); + for (int i = 0; i != N; ++i) { + if (i != 0) { + sb.append(','); + } + sb.append(javaSignature(argTypes[i])); + } + sb.append(')'); + return sb.toString(); + } + + private MemberBox findExplicitFunction(String name, boolean isStatic) + { + int sigStart = name.indexOf('('); + if (sigStart < 0) { return null; } + + Hashtable ht = isStatic ? staticMembers : members; + MemberBox[] methodsOrCtors = null; + boolean isCtor = (isStatic && sigStart == 0); + + if (isCtor) { + // Explicit request for an overloaded constructor + methodsOrCtors = ctors; + } else { + // Explicit request for an overloaded method + String trueName = name.substring(0,sigStart); + Object obj = ht.get(trueName); + if (!isStatic && obj == null) { + // Try to get static member from instance (LC3) + obj = staticMembers.get(trueName); + } + if (obj instanceof NativeJavaMethod) { + NativeJavaMethod njm = (NativeJavaMethod)obj; + methodsOrCtors = njm.methods; + } + } + + if (methodsOrCtors != null) { + for (int i = 0; i < methodsOrCtors.length; i++) { + Class[] type = methodsOrCtors[i].argTypes; + String sig = liveConnectSignature(type); + if (sigStart + sig.length() == name.length() + && name.regionMatches(sigStart, sig, 0, sig.length())) + { + return methodsOrCtors[i]; + } + } + } + + return null; + } + + private Object getExplicitFunction(Scriptable scope, String name, + Object javaObject, boolean isStatic) + { + Hashtable ht = isStatic ? staticMembers : members; + Object member = null; + MemberBox methodOrCtor = findExplicitFunction(name, isStatic); + + if (methodOrCtor != null) { + Scriptable prototype = + ScriptableObject.getFunctionPrototype(scope); + + if (methodOrCtor.isCtor()) { + NativeJavaConstructor fun = + new NativeJavaConstructor(methodOrCtor); + fun.setPrototype(prototype); + member = fun; + ht.put(name, fun); + } else { + String trueName = methodOrCtor.getName(); + member = ht.get(trueName); + + if (member instanceof NativeJavaMethod && + ((NativeJavaMethod)member).methods.length > 1 ) { + NativeJavaMethod fun = + new NativeJavaMethod(methodOrCtor, name); + fun.setPrototype(prototype); + ht.put(name, fun); + member = fun; + } + } + } + + return member; + } + + /** + * Retrieves mapping of methods to accessible methods for a class. + * In case the class is not public, retrieves methods with same + * signature as its public methods from public superclasses and + * interfaces (if they exist). Basically upcasts every method to the + * nearest accessible method. + */ + private static Method[] discoverAccessibleMethods(Class clazz, + boolean includeProtected, + boolean includePrivate) + { + Map map = new HashMap(); + discoverAccessibleMethods(clazz, map, includeProtected, includePrivate); + return (Method[])map.values().toArray(new Method[map.size()]); + } + + private static void discoverAccessibleMethods(Class clazz, Map map, + boolean includeProtected, + boolean includePrivate) + { + if (Modifier.isPublic(clazz.getModifiers()) || includePrivate) { + try { + if (includeProtected || includePrivate) { + while (clazz != null) { + try { + Method[] methods = clazz.getDeclaredMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + int mods = method.getModifiers(); + + if (Modifier.isPublic(mods) || + Modifier.isProtected(mods) || + includePrivate) + { + if (includePrivate) + method.setAccessible(true); + map.put(new MethodSignature(method), method); + } + } + clazz = clazz.getSuperclass(); + } catch (SecurityException e) { + // Some security settings (i.e., applets) disallow + // access to Class.getDeclaredMethods. Fall back to + // Class.getMethods. + Method[] methods = clazz.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + MethodSignature sig + = new MethodSignature(method); + if (map.get(sig) == null) + map.put(sig, method); + } + break; // getMethods gets superclass methods, no + // need to loop any more + } + } + } else { + Method[] methods = clazz.getMethods(); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + MethodSignature sig = new MethodSignature(method); + map.put(sig, method); + } + } + return; + } catch (SecurityException e) { + Context.reportWarning( + "Could not discover accessible methods of class " + + clazz.getName() + " due to lack of privileges, " + + "attemping superclasses/interfaces."); + // Fall through and attempt to discover superclass/interface + // methods + } + } + + Class[] interfaces = clazz.getInterfaces(); + for (int i = 0; i < interfaces.length; i++) { + discoverAccessibleMethods(interfaces[i], map, includeProtected, + includePrivate); + } + Class superclass = clazz.getSuperclass(); + if (superclass != null) { + discoverAccessibleMethods(superclass, map, includeProtected, + includePrivate); + } + } + + private static final class MethodSignature + { + private final String name; + private final Class[] args; + + private MethodSignature(String name, Class[] args) + { + this.name = name; + this.args = args; + } + + MethodSignature(Method method) + { + this(method.getName(), method.getParameterTypes()); + } + + public boolean equals(Object o) + { + if(o instanceof MethodSignature) + { + MethodSignature ms = (MethodSignature)o; + return ms.name.equals(name) && Arrays.equals(args, ms.args); + } + return false; + } + + public int hashCode() + { + return name.hashCode() ^ args.length; + } + } + + private void reflect(Scriptable scope, boolean includeProtected) + { + // We reflect methods first, because we want overloaded field/method + // names to be allocated to the NativeJavaMethod before the field + // gets in the way. + + Method[] methods = discoverAccessibleMethods(cl, includeProtected, + includePrivate); + for (int i = 0; i < methods.length; i++) { + Method method = methods[i]; + int mods = method.getModifiers(); + boolean isStatic = Modifier.isStatic(mods); + Hashtable ht = isStatic ? staticMembers : members; + String name = method.getName(); + Object value = ht.get(name); + if (value == null) { + ht.put(name, method); + } else { + ObjArray overloadedMethods; + if (value instanceof ObjArray) { + overloadedMethods = (ObjArray)value; + } else { + if (!(value instanceof Method)) Kit.codeBug(); + // value should be instance of Method as at this stage + // staticMembers and members can only contain methods + overloadedMethods = new ObjArray(); + overloadedMethods.add(value); + ht.put(name, overloadedMethods); + } + overloadedMethods.add(method); + } + } + + // replace Method instances by wrapped NativeJavaMethod objects + // first in staticMembers and then in members + for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { + boolean isStatic = (tableCursor == 0); + Hashtable ht = (isStatic) ? staticMembers : members; + Enumeration e = ht.keys(); + while (e.hasMoreElements()) { + String name = (String)e.nextElement(); + MemberBox[] methodBoxes; + Object value = ht.get(name); + if (value instanceof Method) { + methodBoxes = new MemberBox[1]; + methodBoxes[0] = new MemberBox((Method)value); + } else { + ObjArray overloadedMethods = (ObjArray)value; + int N = overloadedMethods.size(); + if (N < 2) Kit.codeBug(); + methodBoxes = new MemberBox[N]; + for (int i = 0; i != N; ++i) { + Method method = (Method)overloadedMethods.get(i); + methodBoxes[i] = new MemberBox(method); + } + } + NativeJavaMethod fun = new NativeJavaMethod(methodBoxes); + if (scope != null) { + ScriptRuntime.setFunctionProtoAndParent(fun, scope); + } + ht.put(name, fun); + } + } + + // Reflect fields. + Field[] fields = getAccessibleFields(); + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + String name = field.getName(); + int mods = field.getModifiers(); + if (!includePrivate && !Modifier.isPublic(mods)) { + continue; + } + try { + boolean isStatic = Modifier.isStatic(mods); + Hashtable ht = isStatic ? staticMembers : members; + Object member = ht.get(name); + if (member == null) { + ht.put(name, field); + } else if (member instanceof NativeJavaMethod) { + NativeJavaMethod method = (NativeJavaMethod) member; + FieldAndMethods fam + = new FieldAndMethods(scope, method.methods, field); + Hashtable fmht = isStatic ? staticFieldAndMethods + : fieldAndMethods; + if (fmht == null) { + fmht = new Hashtable(4); + if (isStatic) { + staticFieldAndMethods = fmht; + } else { + fieldAndMethods = fmht; + } + } + fmht.put(name, fam); + ht.put(name, fam); + } else if (member instanceof Field) { + Field oldField = (Field) member; + // If this newly reflected field shadows an inherited field, + // then replace it. Otherwise, since access to the field + // would be ambiguous from Java, no field should be + // reflected. + // For now, the first field found wins, unless another field + // explicitly shadows it. + if (oldField.getDeclaringClass(). + isAssignableFrom(field.getDeclaringClass())) + { + ht.put(name, field); + } + } else { + // "unknown member type" + Kit.codeBug(); + } + } catch (SecurityException e) { + // skip this field + Context.reportWarning("Could not access field " + + name + " of class " + cl.getName() + + " due to lack of privileges."); + } + } + + // Create bean propeties from corresponding get/set methods first for + // static members and then for instance members + for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { + boolean isStatic = (tableCursor == 0); + Hashtable ht = (isStatic) ? staticMembers : members; + + Hashtable toAdd = new Hashtable(); + + // Now, For each member, make "bean" properties. + for (Enumeration e = ht.keys(); e.hasMoreElements(); ) { + + // Is this a getter? + String name = (String) e.nextElement(); + boolean memberIsGetMethod = name.startsWith("get"); + boolean memberIsSetMethod = name.startsWith("set"); + boolean memberIsIsMethod = name.startsWith("is"); + if (memberIsGetMethod || memberIsIsMethod + || memberIsSetMethod) { + // Double check name component. + String nameComponent + = name.substring(memberIsIsMethod ? 2 : 3); + if (nameComponent.length() == 0) + continue; + + // Make the bean property name. + String beanPropertyName = nameComponent; + char ch0 = nameComponent.charAt(0); + if (Character.isUpperCase(ch0)) { + if (nameComponent.length() == 1) { + beanPropertyName = nameComponent.toLowerCase(); + } else { + char ch1 = nameComponent.charAt(1); + if (!Character.isUpperCase(ch1)) { + beanPropertyName = Character.toLowerCase(ch0) + +nameComponent.substring(1); + } + } + } + + // If we already have a member by this name, don't do this + // property. + if (ht.containsKey(beanPropertyName) + || toAdd.containsKey(beanPropertyName)) { + continue; + } + + // Find the getter method, or if there is none, the is- + // method. + MemberBox getter = null; + getter = findGetter(isStatic, ht, "get", nameComponent); + // If there was no valid getter, check for an is- method. + if (getter == null) { + getter = findGetter(isStatic, ht, "is", nameComponent); + } + + // setter + MemberBox setter = null; + NativeJavaMethod setters = null; + String setterName = "set".concat(nameComponent); + + if (ht.containsKey(setterName)) { + // Is this value a method? + Object member = ht.get(setterName); + if (member instanceof NativeJavaMethod) { + NativeJavaMethod njmSet = (NativeJavaMethod)member; + if (getter != null) { + // We have a getter. Now, do we have a matching + // setter? + Class type = getter.method().getReturnType(); + setter = extractSetMethod(type, njmSet.methods, + isStatic); + } else { + // No getter, find any set method + setter = extractSetMethod(njmSet.methods, + isStatic); + } + if (njmSet.methods.length > 1) { + setters = njmSet; + } + } + } + // Make the property. + BeanProperty bp = new BeanProperty(getter, setter, + setters); + toAdd.put(beanPropertyName, bp); + } + } + + // Add the new bean properties. + for (Enumeration e = toAdd.keys(); e.hasMoreElements();) { + Object key = e.nextElement(); + Object value = toAdd.get(key); + ht.put(key, value); + } + } + + // Reflect constructors + Constructor[] constructors = getAccessibleConstructors(); + ctors = new MemberBox[constructors.length]; + for (int i = 0; i != constructors.length; ++i) { + ctors[i] = new MemberBox(constructors[i]); + } + } + + private Constructor[] getAccessibleConstructors() + { + // The JVM currently doesn't allow changing access on java.lang.Class + // constructors, so don't try + if (includePrivate && cl != ScriptRuntime.ClassClass) { + try { + Constructor[] cons = cl.getDeclaredConstructors(); + Constructor.setAccessible(cons, true); + + return cons; + } catch (SecurityException e) { + // Fall through to !includePrivate case + Context.reportWarning("Could not access constructor " + + " of class " + cl.getName() + + " due to lack of privileges."); + } + } + return cl.getConstructors(); + } + + private Field[] getAccessibleFields() { + if (includePrivate) { + try { + ArrayList fieldsList = new ArrayList(); + Class currentClass = cl; + + while (currentClass != null) { + // get all declared fields in this class, make them + // accessible, and save + Field[] declared = currentClass.getDeclaredFields(); + for (int i = 0; i < declared.length; i++) { + declared[i].setAccessible(true); + fieldsList.add(declared[i]); + } + // walk up superclass chain. no need to deal specially with + // interfaces, since they can't have fields + currentClass = currentClass.getSuperclass(); + } + + return (Field[]) fieldsList.toArray( + new Field[fieldsList.size()]); + } catch (SecurityException e) { + // fall through to !includePrivate case + } + } + return cl.getFields(); + } + + private MemberBox findGetter(boolean isStatic, Hashtable ht, String prefix, + String propertyName) + { + String getterName = prefix.concat(propertyName); + if (ht.containsKey(getterName)) { + // Check that the getter is a method. + Object member = ht.get(getterName); + if (member instanceof NativeJavaMethod) { + NativeJavaMethod njmGet = (NativeJavaMethod) member; + return extractGetMethod(njmGet.methods, isStatic); + } + } + return null; + } + + private static MemberBox extractGetMethod(MemberBox[] methods, + boolean isStatic) + { + // Inspect the list of all MemberBox for the only one having no + // parameters + for (int methodIdx = 0; methodIdx < methods.length; methodIdx++) { + MemberBox method = methods[methodIdx]; + // Does getter method have an empty parameter list with a return + // value (eg. a getSomething() or isSomething())? + if (method.argTypes.length == 0 + && (!isStatic || method.isStatic())) + { + Class type = method.method().getReturnType(); + if (type != Void.TYPE) { + return method; + } + break; + } + } + return null; + } + + private static MemberBox extractSetMethod(Class type, MemberBox[] methods, + boolean isStatic) + { + // + // Note: it may be preferable to allow NativeJavaMethod.findFunction() + // to find the appropriate setter; unfortunately, it requires an + // instance of the target arg to determine that. + // + + // Make two passes: one to find a method with direct type assignment, + // and one to find a widening conversion. + for (int pass = 1; pass <= 2; ++pass) { + for (int i = 0; i < methods.length; ++i) { + MemberBox method = methods[i]; + if (!isStatic || method.isStatic()) { + Class[] params = method.argTypes; + if (params.length == 1) { + if (pass == 1) { + if (params[0] == type) { + return method; + } + } else { + if (pass != 2) Kit.codeBug(); + if (params[0].isAssignableFrom(type)) { + return method; + } + } + } + } + } + } + return null; + } + + private static MemberBox extractSetMethod(MemberBox[] methods, + boolean isStatic) + { + + for (int i = 0; i < methods.length; ++i) { + MemberBox method = methods[i]; + if (!isStatic || method.isStatic()) { + if (method.method().getReturnType() == Void.TYPE) { + if (method.argTypes.length == 1) { + return method; + } + } + } + } + return null; + } + + Hashtable getFieldAndMethodsObjects(Scriptable scope, Object javaObject, + boolean isStatic) + { + Hashtable ht = isStatic ? staticFieldAndMethods : fieldAndMethods; + if (ht == null) + return null; + int len = ht.size(); + Hashtable result = new Hashtable(len); + Enumeration e = ht.elements(); + while (len-- > 0) { + FieldAndMethods fam = (FieldAndMethods) e.nextElement(); + FieldAndMethods famNew = new FieldAndMethods(scope, fam.methods, + fam.field); + famNew.javaObject = javaObject; + result.put(fam.field.getName(), famNew); + } + return result; + } + + static JavaMembers lookupClass(Scriptable scope, Class dynamicType, + Class staticType, boolean includeProtected) + { + JavaMembers members; + scope = ScriptableObject.getTopLevelScope(scope); + ClassCache cache = ClassCache.get(scope); + Map<Class<?>,JavaMembers> ct = cache.getClassCacheMap(); + + Class cl = dynamicType; + for (;;) { + members = ct.get(cl); + if (members != null) { + return members; + } + try { + members = new JavaMembers(scope, cl, includeProtected); + break; + } catch (SecurityException e) { + // Reflection may fail for objects that are in a restricted + // access package (e.g. sun.*). If we get a security + // exception, try again with the static type if it is interface. + // Otherwise, try superclass + if (staticType != null && staticType.isInterface()) { + cl = staticType; + staticType = null; // try staticType only once + } else { + Class parent = cl.getSuperclass(); + if (parent == null) { + if (cl.isInterface()) { + // last resort after failed staticType interface + parent = ScriptRuntime.ObjectClass; + } else { + throw e; + } + } + cl = parent; + } + } + } + + if (cache.isCachingEnabled()) + ct.put(cl, members); + return members; + } + + RuntimeException reportMemberNotFound(String memberName) + { + return Context.reportRuntimeError2( + "msg.java.member.not.found", cl.getName(), memberName); + } + + private Class cl; + private Hashtable members; + private Hashtable fieldAndMethods; + private Hashtable staticMembers; + private Hashtable staticFieldAndMethods; + MemberBox[] ctors; + private boolean includePrivate; +} + +class BeanProperty +{ + BeanProperty(MemberBox getter, MemberBox setter, NativeJavaMethod setters) + { + this.getter = getter; + this.setter = setter; + this.setters = setters; + } + + MemberBox getter; + MemberBox setter; + NativeJavaMethod setters; +} + +class FieldAndMethods extends NativeJavaMethod +{ + static final long serialVersionUID = -9222428244284796755L; + + FieldAndMethods(Scriptable scope, MemberBox[] methods, Field field) + { + super(methods); + this.field = field; + setParentScope(scope); + setPrototype(ScriptableObject.getFunctionPrototype(scope)); + } + + public Object getDefaultValue(Class hint) + { + if (hint == ScriptRuntime.FunctionClass) + return this; + Object rval; + Class type; + try { + rval = field.get(javaObject); + type = field.getType(); + } catch (IllegalAccessException accEx) { + throw Context.reportRuntimeError1( + "msg.java.internal.private", field.getName()); + } + Context cx = Context.getContext(); + rval = cx.getWrapFactory().wrap(cx, this, rval, type); + if (rval instanceof Scriptable) { + rval = ((Scriptable) rval).getDefaultValue(hint); + } + return rval; + } + + Field field; + Object javaObject; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaScriptException.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaScriptException.java new file mode 100644 index 0000000..11ebedf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/JavaScriptException.java @@ -0,0 +1,117 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Bojan Cekrlic + * Hannes Wallnoefer + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Java reflection of JavaScript exceptions. + * Instances of this class are thrown by the JavaScript 'throw' keyword. + * + * @author Mike McCabe + */ +public class JavaScriptException extends RhinoException +{ + static final long serialVersionUID = -7666130513694669293L; + + /** + * @deprecated + * Use {@link WrappedException#WrappedException(Throwable)} to report + * exceptions in Java code. + */ + public JavaScriptException(Object value) + { + this(value, "", 0); + } + + /** + * Create a JavaScript exception wrapping the given JavaScript value + * + * @param value the JavaScript value thrown. + */ + public JavaScriptException(Object value, String sourceName, int lineNumber) + { + recordErrorOrigin(sourceName, lineNumber, null, 0); + this.value = value; + } + + public String details() + { + try { + return ScriptRuntime.toString(value); + } catch (RuntimeException rte) { + // ScriptRuntime.toString may throw a RuntimeException + if (value == null) { + return "null"; + } else if (value instanceof Scriptable) { + return ScriptRuntime.defaultObjectToString((Scriptable)value); + } else { + return value.toString(); + } + } + } + + /** + * @return the value wrapped by this exception + */ + public Object getValue() + { + return value; + } + + /** + * @deprecated Use {@link RhinoException#sourceName()} from the super class. + */ + public String getSourceName() + { + return sourceName(); + } + + /** + * @deprecated Use {@link RhinoException#lineNumber()} from the super class. + */ + public int getLineNumber() + { + return lineNumber(); + } + + private Object value; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Kit.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Kit.java new file mode 100644 index 0000000..f7b4cad --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Kit.java @@ -0,0 +1,486 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.lang.reflect.Method; +import java.util.Hashtable; + +/** + * Collection of utilities + */ + +public class Kit +{ + /** + * Reflection of Throwable.initCause(Throwable) from JDK 1.4 + * or nul if it is not available. + */ + private static Method Throwable_initCause = null; + + static { + // Are we running on a JDK 1.4 or later system? + try { + Class ThrowableClass = Kit.classOrNull("java.lang.Throwable"); + Class[] signature = { ThrowableClass }; + Throwable_initCause + = ThrowableClass.getMethod("initCause", signature); + } catch (Exception ex) { + // Assume any exceptions means the method does not exist. + } + } + + public static Class classOrNull(String className) + { + try { + return Class.forName(className); + } catch (ClassNotFoundException ex) { + } catch (SecurityException ex) { + } catch (LinkageError ex) { + } catch (IllegalArgumentException e) { + // Can be thrown if name has characters that a class name + // can not contain + } + return null; + } + + public static Class classOrNull(ClassLoader loader, String className) + { + try { + return loader.loadClass(className); + } catch (ClassNotFoundException ex) { + } catch (SecurityException ex) { + } catch (LinkageError ex) { + } catch (IllegalArgumentException e) { + // Can be thrown if name has characters that a class name + // can not contain + } + return null; + } + + static Object newInstanceOrNull(Class cl) + { + try { + return cl.newInstance(); + } catch (SecurityException x) { + } catch (LinkageError ex) { + } catch (InstantiationException x) { + } catch (IllegalAccessException x) { + } + return null; + } + + /** + * Check that testClass is accesible from the given loader. + */ + static boolean testIfCanLoadRhinoClasses(ClassLoader loader) + { + Class testClass = ScriptRuntime.ContextFactoryClass; + Class x = Kit.classOrNull(loader, testClass.getName()); + if (x != testClass) { + // The check covers the case when x == null => + // loader does not know about testClass or the case + // when x != null && x != testClass => + // loader loads a class unrelated to testClass + return false; + } + return true; + } + + /** + * If initCause methods exists in Throwable, call + * <tt>ex.initCause(cause)</tt> or otherwise do nothing. + * @return The <tt>ex</tt> argument. + */ + public static RuntimeException initCause(RuntimeException ex, + Throwable cause) + { + if (Throwable_initCause != null) { + Object[] args = { cause }; + try { + Throwable_initCause.invoke(ex, args); + } catch (Exception e) { + // Ignore any exceptions + } + } + return ex; + } + + /** + * Split string into array of strings using semicolon as string terminator + * (; after the last string is required). + */ + public static String[] semicolonSplit(String s) + { + String[] array = null; + for (;;) { + // loop 2 times: first to count semicolons and then to fill array + int count = 0; + int cursor = 0; + for (;;) { + int next = s.indexOf(';', cursor); + if (next < 0) { + break; + } + if (array != null) { + array[count] = s.substring(cursor, next); + } + ++count; + cursor = next + 1; + } + // after the last semicolon + if (array == null) { + // array size counting state: + // check for required terminating ';' + if (cursor != s.length()) + throw new IllegalArgumentException(); + array = new String[count]; + } else { + // array filling state: stop the loop + break; + } + } + return array; + } + + /** + * If character <tt>c</tt> is a hexadecimal digit, return + * <tt>accumulator</tt> * 16 plus corresponding + * number. Otherise return -1. + */ + public static int xDigitToInt(int c, int accumulator) + { + check: { + // Use 0..9 < A..Z < a..z + if (c <= '9') { + c -= '0'; + if (0 <= c) { break check; } + } else if (c <= 'F') { + if ('A' <= c) { + c -= ('A' - 10); + break check; + } + } else if (c <= 'f') { + if ('a' <= c) { + c -= ('a' - 10); + break check; + } + } + return -1; + } + return (accumulator << 4) | c; + } + + /** + * Add <i>listener</i> to <i>bag</i> of listeners. + * The function does not modify <i>bag</i> and return a new collection + * containing <i>listener</i> and all listeners from <i>bag</i>. + * Bag without listeners always represented as the null value. + * <p> + * Usage example: + * <pre> + * private volatile Object changeListeners; + * + * public void addMyListener(PropertyChangeListener l) + * { + * synchronized (this) { + * changeListeners = Kit.addListener(changeListeners, l); + * } + * } + * + * public void removeTextListener(PropertyChangeListener l) + * { + * synchronized (this) { + * changeListeners = Kit.removeListener(changeListeners, l); + * } + * } + * + * public void fireChangeEvent(Object oldValue, Object newValue) + * { + * // Get immune local copy + * Object listeners = changeListeners; + * if (listeners != null) { + * PropertyChangeEvent e = new PropertyChangeEvent( + * this, "someProperty" oldValue, newValue); + * for (int i = 0; ; ++i) { + * Object l = Kit.getListener(listeners, i); + * if (l == null) + * break; + * ((PropertyChangeListener)l).propertyChange(e); + * } + * } + * } + * </pre> + * + * @param listener Listener to add to <i>bag</i> + * @param bag Current collection of listeners. + * @return A new bag containing all listeners from <i>bag</i> and + * <i>listener</i>. + * @see #removeListener(Object bag, Object listener) + * @see #getListener(Object bag, int index) + */ + public static Object addListener(Object bag, Object listener) + { + if (listener == null) throw new IllegalArgumentException(); + if (listener instanceof Object[]) throw new IllegalArgumentException(); + + if (bag == null) { + bag = listener; + } else if (!(bag instanceof Object[])) { + bag = new Object[] { bag, listener }; + } else { + Object[] array = (Object[])bag; + int L = array.length; + // bag has at least 2 elements if it is array + if (L < 2) throw new IllegalArgumentException(); + Object[] tmp = new Object[L + 1]; + System.arraycopy(array, 0, tmp, 0, L); + tmp[L] = listener; + bag = tmp; + } + + return bag; + } + + /** + * Remove <i>listener</i> from <i>bag</i> of listeners. + * The function does not modify <i>bag</i> and return a new collection + * containing all listeners from <i>bag</i> except <i>listener</i>. + * If <i>bag</i> does not contain <i>listener</i>, the function returns + * <i>bag</i>. + * <p> + * For usage example, see {@link #addListener(Object bag, Object listener)}. + * + * @param listener Listener to remove from <i>bag</i> + * @param bag Current collection of listeners. + * @return A new bag containing all listeners from <i>bag</i> except + * <i>listener</i>. + * @see #addListener(Object bag, Object listener) + * @see #getListener(Object bag, int index) + */ + public static Object removeListener(Object bag, Object listener) + { + if (listener == null) throw new IllegalArgumentException(); + if (listener instanceof Object[]) throw new IllegalArgumentException(); + + if (bag == listener) { + bag = null; + } else if (bag instanceof Object[]) { + Object[] array = (Object[])bag; + int L = array.length; + // bag has at least 2 elements if it is array + if (L < 2) throw new IllegalArgumentException(); + if (L == 2) { + if (array[1] == listener) { + bag = array[0]; + } else if (array[0] == listener) { + bag = array[1]; + } + } else { + int i = L; + do { + --i; + if (array[i] == listener) { + Object[] tmp = new Object[L - 1]; + System.arraycopy(array, 0, tmp, 0, i); + System.arraycopy(array, i + 1, tmp, i, L - (i + 1)); + bag = tmp; + break; + } + } while (i != 0); + } + } + + return bag; + } + + /** + * Get listener at <i>index</i> position in <i>bag</i> or null if + * <i>index</i> equals to number of listeners in <i>bag</i>. + * <p> + * For usage example, see {@link #addListener(Object bag, Object listener)}. + * + * @param bag Current collection of listeners. + * @param index Index of the listener to access. + * @return Listener at the given index or null. + * @see #addListener(Object bag, Object listener) + * @see #removeListener(Object bag, Object listener) + */ + public static Object getListener(Object bag, int index) + { + if (index == 0) { + if (bag == null) + return null; + if (!(bag instanceof Object[])) + return bag; + Object[] array = (Object[])bag; + // bag has at least 2 elements if it is array + if (array.length < 2) throw new IllegalArgumentException(); + return array[0]; + } else if (index == 1) { + if (!(bag instanceof Object[])) { + if (bag == null) throw new IllegalArgumentException(); + return null; + } + Object[] array = (Object[])bag; + // the array access will check for index on its own + return array[1]; + } else { + // bag has to array + Object[] array = (Object[])bag; + int L = array.length; + if (L < 2) throw new IllegalArgumentException(); + if (index == L) + return null; + return array[index]; + } + } + + static Object initHash(Hashtable h, Object key, Object initialValue) + { + synchronized (h) { + Object current = h.get(key); + if (current == null) { + h.put(key, initialValue); + } else { + initialValue = current; + } + } + return initialValue; + } + + private final static class ComplexKey + { + private Object key1; + private Object key2; + private int hash; + + ComplexKey(Object key1, Object key2) + { + this.key1 = key1; + this.key2 = key2; + } + + public boolean equals(Object anotherObj) + { + if (!(anotherObj instanceof ComplexKey)) + return false; + ComplexKey another = (ComplexKey)anotherObj; + return key1.equals(another.key1) && key2.equals(another.key2); + } + + public int hashCode() + { + if (hash == 0) { + hash = key1.hashCode() ^ key2.hashCode(); + } + return hash; + } + } + + public static Object makeHashKeyFromPair(Object key1, Object key2) + { + if (key1 == null) throw new IllegalArgumentException(); + if (key2 == null) throw new IllegalArgumentException(); + return new ComplexKey(key1, key2); + } + + public static String readReader(Reader r) + throws IOException + { + char[] buffer = new char[512]; + int cursor = 0; + for (;;) { + int n = r.read(buffer, cursor, buffer.length - cursor); + if (n < 0) { break; } + cursor += n; + if (cursor == buffer.length) { + char[] tmp = new char[buffer.length * 2]; + System.arraycopy(buffer, 0, tmp, 0, cursor); + buffer = tmp; + } + } + return new String(buffer, 0, cursor); + } + + public static byte[] readStream(InputStream is, int initialBufferCapacity) + throws IOException + { + if (initialBufferCapacity <= 0) { + throw new IllegalArgumentException( + "Bad initialBufferCapacity: "+initialBufferCapacity); + } + byte[] buffer = new byte[initialBufferCapacity]; + int cursor = 0; + for (;;) { + int n = is.read(buffer, cursor, buffer.length - cursor); + if (n < 0) { break; } + cursor += n; + if (cursor == buffer.length) { + byte[] tmp = new byte[buffer.length * 2]; + System.arraycopy(buffer, 0, tmp, 0, cursor); + buffer = tmp; + } + } + if (cursor != buffer.length) { + byte[] tmp = new byte[cursor]; + System.arraycopy(buffer, 0, tmp, 0, cursor); + buffer = tmp; + } + return buffer; + } + + /** + * Throws RuntimeException to indicate failed assertion. + * The function never returns and its return type is RuntimeException + * only to be able to write <tt>throw Kit.codeBug()</tt> if plain + * <tt>Kit.codeBug()</tt> triggers unreachable code error. + */ + public static RuntimeException codeBug() + throws RuntimeException + { + RuntimeException ex = new IllegalStateException("FAILED ASSERTION"); + // Print stack trace ASAP + ex.printStackTrace(System.err); + throw ex; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/LazilyLoadedCtor.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/LazilyLoadedCtor.java new file mode 100644 index 0000000..4153372 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/LazilyLoadedCtor.java @@ -0,0 +1,136 @@ +/* -*- 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): + * Norris Boyd + * Igor Bukanov + * + * 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.*; + +/** + * Avoid loading classes unless they are used. + * + * <p> This improves startup time and average memory usage. + */ +public final class LazilyLoadedCtor implements java.io.Serializable { + private static final long serialVersionUID = 1L; + + private static final int STATE_BEFORE_INIT = 0; + private static final int STATE_INITIALIZING = 1; + private static final int STATE_WITH_VALUE = 2; + + private final ScriptableObject scope; + private final String propertyName; + private final String className; + private final boolean sealed; + private Object initializedValue; + private int state; + + public LazilyLoadedCtor(ScriptableObject scope, String propertyName, + String className, boolean sealed) + { + + this.scope = scope; + this.propertyName = propertyName; + this.className = className; + this.sealed = sealed; + this.state = STATE_BEFORE_INIT; + + scope.addLazilyInitializedValue(propertyName, 0, this, + ScriptableObject.DONTENUM); + } + + void init() + { + synchronized (this) { + if (state == STATE_INITIALIZING) + throw new IllegalStateException( + "Recursive initialization for "+propertyName); + if (state == STATE_BEFORE_INIT) { + state = STATE_INITIALIZING; + // Set value now to have something to set in finally block if + // buildValue throws. + Object value = Scriptable.NOT_FOUND; + try { + value = buildValue(); + } finally { + initializedValue = value; + state = STATE_WITH_VALUE; + } + } + } + } + + Object getValue() + { + if (state != STATE_WITH_VALUE) + throw new IllegalStateException(propertyName); + return initializedValue; + } + + private Object buildValue() + { + Class cl = Kit.classOrNull(className); + if (cl != null) { + try { + Object value = ScriptableObject.buildClassCtor(scope, cl, + sealed, false); + if (value != null) { + return value; + } + else { + // cl has own static initializer which is expected + // to set the property on its own. + value = scope.get(propertyName, scope); + if (value != Scriptable.NOT_FOUND) + return value; + } + } catch (InvocationTargetException ex) { + Throwable target = ex.getTargetException(); + if (target instanceof RuntimeException) { + throw (RuntimeException)target; + } + } catch (RhinoException ex) { + } catch (InstantiationException ex) { + } catch (IllegalAccessException ex) { + } catch (SecurityException ex) { + } + } + return Scriptable.NOT_FOUND; + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/MemberBox.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/MemberBox.java new file mode 100644 index 0000000..2d3553f --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/MemberBox.java @@ -0,0 +1,362 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Felix Meschberger + * Norris Boyd + * Ulrike Mueller <umueller@demandware.com> + * + * 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.io.*; + +/** + * Wrappper class for Method and Constructor instances to cache + * getParameterTypes() results, recover from IllegalAccessException + * in some cases and provide serialization support. + * + * @author Igor Bukanov + */ + +final class MemberBox implements Serializable +{ + static final long serialVersionUID = 6358550398665688245L; + + private transient Member memberObject; + transient Class[] argTypes; + transient Object delegateTo; + transient boolean vararg; + + + MemberBox(Method method) + { + init(method); + } + + MemberBox(Constructor constructor) + { + init(constructor); + } + + private void init(Method method) + { + this.memberObject = method; + this.argTypes = method.getParameterTypes(); + this.vararg = VMBridge.instance.isVarArgs(method); + } + + private void init(Constructor constructor) + { + this.memberObject = constructor; + this.argTypes = constructor.getParameterTypes(); + this.vararg = VMBridge.instance.isVarArgs(constructor); + } + + Method method() + { + return (Method)memberObject; + } + + Constructor ctor() + { + return (Constructor)memberObject; + } + + Member member() + { + return memberObject; + } + + boolean isMethod() + { + return memberObject instanceof Method; + } + + boolean isCtor() + { + return memberObject instanceof Constructor; + } + + boolean isStatic() + { + return Modifier.isStatic(memberObject.getModifiers()); + } + + String getName() + { + return memberObject.getName(); + } + + Class getDeclaringClass() + { + return memberObject.getDeclaringClass(); + } + + String toJavaDeclaration() + { + StringBuffer sb = new StringBuffer(); + if (isMethod()) { + Method method = method(); + sb.append(method.getReturnType()); + sb.append(' '); + sb.append(method.getName()); + } else { + Constructor ctor = ctor(); + String name = ctor.getDeclaringClass().getName(); + int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + name = name.substring(lastDot + 1); + } + sb.append(name); + } + sb.append(JavaMembers.liveConnectSignature(argTypes)); + return sb.toString(); + } + + public String toString() + { + return memberObject.toString(); + } + + Object invoke(Object target, Object[] args) + { + Method method = method(); + try { + try { + return method.invoke(target, args); + } catch (IllegalAccessException ex) { + Method accessible = searchAccessibleMethod(method, argTypes); + if (accessible != null) { + memberObject = accessible; + method = accessible; + } else { + if (!VMBridge.instance.tryToMakeAccessible(method)) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + // Retry after recovery + return method.invoke(target, args); + } + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + + Object newInstance(Object[] args) + { + Constructor ctor = ctor(); + try { + try { + return ctor.newInstance(args); + } catch (IllegalAccessException ex) { + if (!VMBridge.instance.tryToMakeAccessible(ctor)) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + return ctor.newInstance(args); + } catch (Exception ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } + } + + private static Method searchAccessibleMethod(Method method, Class[] params) + { + int modifiers = method.getModifiers(); + if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) { + Class c = method.getDeclaringClass(); + if (!Modifier.isPublic(c.getModifiers())) { + String name = method.getName(); + Class[] intfs = c.getInterfaces(); + for (int i = 0, N = intfs.length; i != N; ++i) { + Class intf = intfs[i]; + if (Modifier.isPublic(intf.getModifiers())) { + try { + return intf.getMethod(name, params); + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { } + } + } + for (;;) { + c = c.getSuperclass(); + if (c == null) { break; } + if (Modifier.isPublic(c.getModifiers())) { + try { + Method m = c.getMethod(name, params); + int mModifiers = m.getModifiers(); + if (Modifier.isPublic(mModifiers) + && !Modifier.isStatic(mModifiers)) + { + return m; + } + } catch (NoSuchMethodException ex) { + } catch (SecurityException ex) { } + } + } + } + } + return null; + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + Member member = readMember(in); + if (member instanceof Method) { + init((Method)member); + } else { + init((Constructor)member); + } + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + writeMember(out, memberObject); + } + + /** + * Writes a Constructor or Method object. + * + * Methods and Constructors are not serializable, so we must serialize + * information about the class, the name, and the parameters and + * recreate upon deserialization. + */ + private static void writeMember(ObjectOutputStream out, Member member) + throws IOException + { + if (member == null) { + out.writeBoolean(false); + return; + } + out.writeBoolean(true); + if (!(member instanceof Method || member instanceof Constructor)) + throw new IllegalArgumentException("not Method or Constructor"); + out.writeBoolean(member instanceof Method); + out.writeObject(member.getName()); + out.writeObject(member.getDeclaringClass()); + if (member instanceof Method) { + writeParameters(out, ((Method) member).getParameterTypes()); + } else { + writeParameters(out, ((Constructor) member).getParameterTypes()); + } + } + + /** + * Reads a Method or a Constructor from the stream. + */ + private static Member readMember(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + if (!in.readBoolean()) + return null; + boolean isMethod = in.readBoolean(); + String name = (String) in.readObject(); + Class declaring = (Class) in.readObject(); + Class[] parms = readParameters(in); + try { + if (isMethod) { + return declaring.getMethod(name, parms); + } else { + return declaring.getConstructor(parms); + } + } catch (NoSuchMethodException e) { + throw new IOException("Cannot find member: " + e); + } + } + + private static final Class[] primitives = { + Boolean.TYPE, + Byte.TYPE, + Character.TYPE, + Double.TYPE, + Float.TYPE, + Integer.TYPE, + Long.TYPE, + Short.TYPE, + Void.TYPE + }; + + /** + * Writes an array of parameter types to the stream. + * + * Requires special handling because primitive types cannot be + * found upon deserialization by the default Java implementation. + */ + private static void writeParameters(ObjectOutputStream out, Class[] parms) + throws IOException + { + out.writeShort(parms.length); + outer: + for (int i=0; i < parms.length; i++) { + Class parm = parms[i]; + boolean primitive = parm.isPrimitive(); + out.writeBoolean(primitive); + if (!primitive) { + out.writeObject(parm); + continue; + } + for (int j=0; j < primitives.length; j++) { + if (parm.equals(primitives[j])) { + out.writeByte(j); + continue outer; + } + } + throw new IllegalArgumentException("Primitive " + parm + + " not found"); + } + } + + /** + * Reads an array of parameter types from the stream. + */ + private static Class[] readParameters(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + Class[] result = new Class[in.readShort()]; + for (int i=0; i < result.length; i++) { + if (!in.readBoolean()) { + result[i] = (Class) in.readObject(); + continue; + } + result[i] = primitives[in.readByte()]; + } + return result; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeArray.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeArray.java new file mode 100644 index 0000000..b170ff4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeArray.java @@ -0,0 +1,1727 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Mike McCabe + * Igor Bukanov + * + * 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.util.Arrays; + +/** + * This class implements the Array native object. + * @author Norris Boyd + * @author Mike McCabe + */ +public class NativeArray extends IdScriptableObject +{ + static final long serialVersionUID = 7331366857676127338L; + + /* + * Optimization possibilities and open issues: + * - Long vs. double schizophrenia. I suspect it might be better + * to use double throughout. + * + * - Functions that need a new Array call "new Array" in the + * current scope rather than using a hardwired constructor; + * "Array" could be redefined. It turns out that js calls the + * equivalent of "new Array" in the current scope, except that it + * always gets at least an object back, even when Array == null. + */ + + private static final Object ARRAY_TAG = new Object(); + private static final Integer NEGATIVE_ONE = new Integer(-1); + + static void init(Scriptable scope, boolean sealed) + { + NativeArray obj = new NativeArray(0); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + static int getMaximumInitialCapacity() { + return maximumInitialCapacity; + } + + static void setMaximumInitialCapacity(int maximumInitialCapacity) { + NativeArray.maximumInitialCapacity = maximumInitialCapacity; + } + + public NativeArray(long lengthArg) + { + denseOnly = lengthArg <= maximumInitialCapacity; + if (denseOnly) { + int intLength = (int) lengthArg; + if (intLength < DEFAULT_INITIAL_CAPACITY) + intLength = DEFAULT_INITIAL_CAPACITY; + dense = new Object[intLength]; + Arrays.fill(dense, Scriptable.NOT_FOUND); + } + length = lengthArg; + } + + public NativeArray(Object[] array) + { + denseOnly = true; + dense = array; + length = array.length; + } + + public String getClassName() + { + return "Array"; + } + + private static final int + Id_length = 1, + MAX_INSTANCE_ID = 1; + + protected int getMaxInstanceId() + { + return MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) + { + if (s.equals("length")) { + return instanceIdInfo(DONTENUM | PERMANENT, Id_length); + } + return super.findInstanceIdInfo(s); + } + + protected String getInstanceIdName(int id) + { + if (id == Id_length) { return "length"; } + return super.getInstanceIdName(id); + } + + protected Object getInstanceIdValue(int id) + { + if (id == Id_length) { + return ScriptRuntime.wrapNumber(length); + } + return super.getInstanceIdValue(id); + } + + protected void setInstanceIdValue(int id, Object value) + { + if (id == Id_length) { + setLength(value); return; + } + super.setInstanceIdValue(id, value); + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_join, + "join", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_reverse, + "reverse", 1); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_sort, + "sort", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_push, + "push", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_pop, + "pop", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_shift, + "shift", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_unshift, + "unshift", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_splice, + "splice", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_concat, + "concat", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_slice, + "slice", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_indexOf, + "indexOf", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_lastIndexOf, + "lastIndexOf", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_every, + "every", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_filter, + "filter", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_forEach, + "forEach", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_map, + "map", 2); + addIdFunctionProperty(ctor, ARRAY_TAG, ConstructorId_some, + "some", 2); + super.fillConstructorProperties(ctor); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toLocaleString: arity=1; s="toLocaleString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_join: arity=1; s="join"; break; + case Id_reverse: arity=0; s="reverse"; break; + case Id_sort: arity=1; s="sort"; break; + case Id_push: arity=1; s="push"; break; + case Id_pop: arity=1; s="pop"; break; + case Id_shift: arity=1; s="shift"; break; + case Id_unshift: arity=1; s="unshift"; break; + case Id_splice: arity=1; s="splice"; break; + case Id_concat: arity=1; s="concat"; break; + case Id_slice: arity=1; s="slice"; break; + case Id_indexOf: arity=1; s="indexOf"; break; + case Id_lastIndexOf: arity=1; s="lastIndexOf"; break; + case Id_every: arity=1; s="every"; break; + case Id_filter: arity=1; s="filter"; break; + case Id_forEach: arity=1; s="forEach"; break; + case Id_map: arity=1; s="map"; break; + case Id_some: arity=1; s="some"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(ARRAY_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(ARRAY_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + again: + for (;;) { + switch (id) { + case ConstructorId_join: + case ConstructorId_reverse: + case ConstructorId_sort: + case ConstructorId_push: + case ConstructorId_pop: + case ConstructorId_shift: + case ConstructorId_unshift: + case ConstructorId_splice: + case ConstructorId_concat: + case ConstructorId_slice: + case ConstructorId_indexOf: + case ConstructorId_lastIndexOf: + case ConstructorId_every: + case ConstructorId_filter: + case ConstructorId_forEach: + case ConstructorId_map: + case ConstructorId_some: { + thisObj = ScriptRuntime.toObject(scope, args[0]); + Object[] newArgs = new Object[args.length-1]; + for (int i=0; i < newArgs.length; i++) + newArgs[i] = args[i+1]; + args = newArgs; + id = -id; + continue again; + } + + case Id_constructor: { + boolean inNewExpr = (thisObj == null); + if (!inNewExpr) { + // IdFunctionObject.construct will set up parent, proto + return f.construct(cx, scope, args); + } + return jsConstructor(cx, scope, args); + } + + case Id_toString: + return toStringHelper(cx, scope, thisObj, + cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE), false); + + case Id_toLocaleString: + return toStringHelper(cx, scope, thisObj, false, true); + + case Id_toSource: + return toStringHelper(cx, scope, thisObj, true, false); + + case Id_join: + return js_join(cx, thisObj, args); + + case Id_reverse: + return js_reverse(cx, thisObj, args); + + case Id_sort: + return js_sort(cx, scope, thisObj, args); + + case Id_push: + return js_push(cx, thisObj, args); + + case Id_pop: + return js_pop(cx, thisObj, args); + + case Id_shift: + return js_shift(cx, thisObj, args); + + case Id_unshift: + return js_unshift(cx, thisObj, args); + + case Id_splice: + return js_splice(cx, scope, thisObj, args); + + case Id_concat: + return js_concat(cx, scope, thisObj, args); + + case Id_slice: + return js_slice(cx, thisObj, args); + + case Id_indexOf: + return indexOfHelper(cx, thisObj, args, false); + + case Id_lastIndexOf: + return indexOfHelper(cx, thisObj, args, true); + + case Id_every: + case Id_filter: + case Id_forEach: + case Id_map: + case Id_some: + return iterativeMethod(cx, id, scope, thisObj, args); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + } + + public Object get(int index, Scriptable start) + { + if (!denseOnly && isGetterOrSetter(null, index, false)) + return super.get(index, start); + if (dense != null && 0 <= index && index < dense.length) + return dense[index]; + return super.get(index, start); + } + + public boolean has(int index, Scriptable start) + { + if (!denseOnly && isGetterOrSetter(null, index, false)) + return super.has(index, start); + if (dense != null && 0 <= index && index < dense.length) + return dense[index] != NOT_FOUND; + return super.has(index, start); + } + + // if id is an array index (ECMA 15.4.0), return the number, + // otherwise return -1L + private static long toArrayIndex(String id) + { + double d = ScriptRuntime.toNumber(id); + if (d == d) { + long index = ScriptRuntime.toUint32(d); + if (index == d && index != 4294967295L) { + // Assume that ScriptRuntime.toString(index) is the same + // as java.lang.Long.toString(index) for long + if (Long.toString(index).equals(id)) { + return index; + } + } + } + return -1; + } + + public void put(String id, Scriptable start, Object value) + { + super.put(id, start, value); + if (start == this) { + // If the object is sealed, super will throw exception + long index = toArrayIndex(id); + if (index >= length) { + length = index + 1; + denseOnly = false; + } + } + } + + private boolean ensureCapacity(int capacity) + { + if (capacity > dense.length) { + if (capacity > MAX_PRE_GROW_SIZE) { + denseOnly = false; + return false; + } + capacity = Math.max(capacity, (int)(dense.length * GROW_FACTOR)); + Object[] newDense = new Object[capacity]; + System.arraycopy(dense, 0, newDense, 0, dense.length); + Arrays.fill(newDense, dense.length, newDense.length, + Scriptable.NOT_FOUND); + dense = newDense; + } + return true; + } + + public void put(int index, Scriptable start, Object value) + { + if (start == this && !isSealed() && dense != null && 0 <= index && + (denseOnly || !isGetterOrSetter(null, index, true))) + { + if (index < dense.length) { + dense[index] = value; + if (this.length <= index) + this.length = (long)index + 1; + return; + } else if (denseOnly && index < dense.length * GROW_FACTOR && + ensureCapacity(index+1)) + { + dense[index] = value; + this.length = (long)index + 1; + return; + } else { + denseOnly = false; + } + } + super.put(index, start, value); + if (start == this) { + // only set the array length if given an array index (ECMA 15.4.0) + if (this.length <= index) { + // avoid overflowing index! + this.length = (long)index + 1; + } + } + } + + public void delete(int index) + { + if (dense != null && 0 <= index && index < dense.length && + !isSealed() && (denseOnly || !isGetterOrSetter(null, index, true))) + { + dense[index] = NOT_FOUND; + } else { + super.delete(index); + } + } + + public Object[] getIds() + { + Object[] superIds = super.getIds(); + if (dense == null) { return superIds; } + int N = dense.length; + long currentLength = length; + if (N > currentLength) { + N = (int)currentLength; + } + if (N == 0) { return superIds; } + int superLength = superIds.length; + Object[] ids = new Object[N + superLength]; + + int presentCount = 0; + for (int i = 0; i != N; ++i) { + // Replace existing elements by their indexes + if (dense[i] != NOT_FOUND) { + ids[presentCount] = new Integer(i); + ++presentCount; + } + } + if (presentCount != N) { + // dense contains deleted elems, need to shrink the result + Object[] tmp = new Object[presentCount + superLength]; + System.arraycopy(ids, 0, tmp, 0, presentCount); + ids = tmp; + } + System.arraycopy(superIds, 0, ids, presentCount, superLength); + return ids; + } + + public Object getDefaultValue(Class hint) + { + if (hint == ScriptRuntime.NumberClass) { + Context cx = Context.getContext(); + if (cx.getLanguageVersion() == Context.VERSION_1_2) + return new Long(length); + } + return super.getDefaultValue(hint); + } + + /** + * See ECMA 15.4.1,2 + */ + private static Object jsConstructor(Context cx, Scriptable scope, + Object[] args) + { + if (args.length == 0) + return new NativeArray(0); + + // Only use 1 arg as first element for version 1.2; for + // any other version (including 1.3) follow ECMA and use it as + // a length. + if (cx.getLanguageVersion() == Context.VERSION_1_2) { + return new NativeArray(args); + } else { + Object arg0 = args[0]; + if (args.length > 1 || !(arg0 instanceof Number)) { + return new NativeArray(args); + } else { + long len = ScriptRuntime.toUint32(arg0); + if (len != ((Number)arg0).doubleValue()) + throw Context.reportRuntimeError0("msg.arraylength.bad"); + return new NativeArray(len); + } + } + } + + public long getLength() { + return length; + } + + /** @deprecated Use {@link #getLength()} instead. */ + public long jsGet_length() { + return getLength(); + } + + /** + * Change the value of the internal flag that determines whether all + * storage is handed by a dense backing array rather than an associative + * store. + * @param denseOnly new value for denseOnly flag + * @throws IllegalArgumentException if an attempt is made to enable + * denseOnly after it was disabled; NativeArray code is not written + * to handle switching back to a dense representation + */ + void setDenseOnly(boolean denseOnly) { + if (denseOnly && !this.denseOnly) + throw new IllegalArgumentException(); + this.denseOnly = denseOnly; + } + + private void setLength(Object val) { + /* XXX do we satisfy this? + * 15.4.5.1 [[Put]](P, V): + * 1. Call the [[CanPut]] method of A with name P. + * 2. If Result(1) is false, return. + * ? + */ + + double d = ScriptRuntime.toNumber(val); + long longVal = ScriptRuntime.toUint32(d); + if (longVal != d) + throw Context.reportRuntimeError0("msg.arraylength.bad"); + + if (denseOnly) { + if (longVal < length) { + // downcast okay because denseOnly + Arrays.fill(dense, (int) longVal, dense.length, NOT_FOUND); + length = longVal; + return; + } else if (longVal < MAX_PRE_GROW_SIZE && + longVal < (length * GROW_FACTOR) && + ensureCapacity((int)longVal)) + { + length = longVal; + return; + } else { + denseOnly = false; + } + } + if (longVal < length) { + // remove all properties between longVal and length + if (length - longVal > 0x1000) { + // assume that the representation is sparse + Object[] e = getIds(); // will only find in object itself + for (int i=0; i < e.length; i++) { + Object id = e[i]; + if (id instanceof String) { + // > MAXINT will appear as string + String strId = (String)id; + long index = toArrayIndex(strId); + if (index >= longVal) + delete(strId); + } else { + int index = ((Integer)id).intValue(); + if (index >= longVal) + delete(index); + } + } + } else { + // assume a dense representation + for (long i = longVal; i < length; i++) { + deleteElem(this, i); + } + } + } + length = longVal; + } + + /* Support for generic Array-ish objects. Most of the Array + * functions try to be generic; anything that has a length + * property is assumed to be an array. + * getLengthProperty returns 0 if obj does not have the length property + * or its value is not convertible to a number. + */ + static long getLengthProperty(Context cx, Scriptable obj) { + // These will both give numeric lengths within Uint32 range. + if (obj instanceof NativeString) { + return ((NativeString)obj).getLength(); + } else if (obj instanceof NativeArray) { + return ((NativeArray)obj).getLength(); + } + return ScriptRuntime.toUint32( + ScriptRuntime.getObjectProp(obj, "length", cx)); + } + + private static Object setLengthProperty(Context cx, Scriptable target, + long length) + { + return ScriptRuntime.setObjectProp( + target, "length", ScriptRuntime.wrapNumber(length), cx); + } + + /* Utility functions to encapsulate index > Integer.MAX_VALUE + * handling. Also avoids unnecessary object creation that would + * be necessary to use the general ScriptRuntime.get/setElem + * functions... though this is probably premature optimization. + */ + private static void deleteElem(Scriptable target, long index) { + int i = (int)index; + if (i == index) { target.delete(i); } + else { target.delete(Long.toString(index)); } + } + + private static Object getElem(Context cx, Scriptable target, long index) + { + if (index > Integer.MAX_VALUE) { + String id = Long.toString(index); + return ScriptRuntime.getObjectProp(target, id, cx); + } else { + return ScriptRuntime.getObjectIndex(target, (int)index, cx); + } + } + + private static void setElem(Context cx, Scriptable target, long index, + Object value) + { + if (index > Integer.MAX_VALUE) { + String id = Long.toString(index); + ScriptRuntime.setObjectProp(target, id, value, cx); + } else { + ScriptRuntime.setObjectIndex(target, (int)index, value, cx); + } + } + + private static String toStringHelper(Context cx, Scriptable scope, + Scriptable thisObj, + boolean toSource, boolean toLocale) + { + /* It's probably redundant to handle long lengths in this + * function; StringBuffers are limited to 2^31 in java. + */ + + long length = getLengthProperty(cx, thisObj); + + StringBuffer result = new StringBuffer(256); + + // whether to return '4,unquoted,5' or '[4, "quoted", 5]' + String separator; + + if (toSource) { + result.append('['); + separator = ", "; + } else { + separator = ","; + } + + boolean haslast = false; + long i = 0; + + boolean toplevel, iterating; + if (cx.iterating == null) { + toplevel = true; + iterating = false; + cx.iterating = new ObjToIntMap(31); + } else { + toplevel = false; + iterating = cx.iterating.has(thisObj); + } + + // Make sure cx.iterating is set to null when done + // so we don't leak memory + try { + if (!iterating) { + cx.iterating.put(thisObj, 0); // stop recursion. + for (i = 0; i < length; i++) { + if (i > 0) result.append(separator); + Object elem = getElem(cx, thisObj, i); + if (elem == null || elem == Undefined.instance) { + haslast = false; + continue; + } + haslast = true; + + if (toSource) { + result.append(ScriptRuntime.uneval(cx, scope, elem)); + + } else if (elem instanceof String) { + String s = (String)elem; + if (toSource) { + result.append('\"'); + result.append(ScriptRuntime.escapeString(s)); + result.append('\"'); + } else { + result.append(s); + } + + } else { + if (toLocale) + { + Callable fun; + Scriptable funThis; + fun = ScriptRuntime.getPropFunctionAndThis( + elem, "toLocaleString", cx); + funThis = ScriptRuntime.lastStoredScriptable(cx); + elem = fun.call(cx, scope, funThis, + ScriptRuntime.emptyArgs); + } + result.append(ScriptRuntime.toString(elem)); + } + } + } + } finally { + if (toplevel) { + cx.iterating = null; + } + } + + if (toSource) { + //for [,,].length behavior; we want toString to be symmetric. + if (!haslast && i > 0) + result.append(", ]"); + else + result.append(']'); + } + return result.toString(); + } + + /** + * See ECMA 15.4.4.3 + */ + private static String js_join(Context cx, Scriptable thisObj, + Object[] args) + { + long llength = getLengthProperty(cx, thisObj); + int length = (int)llength; + if (llength != length) { + throw Context.reportRuntimeError1( + "msg.arraylength.too.big", String.valueOf(llength)); + } + // if no args, use "," as separator + String separator = (args.length < 1 || args[0] == Undefined.instance) + ? "," + : ScriptRuntime.toString(args[0]); + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly) { + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < length; i++) { + if (i != 0) { + sb.append(separator); + } + if (i < na.dense.length) { + Object temp = na.dense[i]; + if (temp != null && temp != Undefined.instance && + temp != Scriptable.NOT_FOUND) + { + sb.append(ScriptRuntime.toString(temp)); + } + } + } + return sb.toString(); + } + } + if (length == 0) { + return ""; + } + String[] buf = new String[length]; + int total_size = 0; + for (int i = 0; i != length; i++) { + Object temp = getElem(cx, thisObj, i); + if (temp != null && temp != Undefined.instance) { + String str = ScriptRuntime.toString(temp); + total_size += str.length(); + buf[i] = str; + } + } + total_size += (length - 1) * separator.length(); + StringBuffer sb = new StringBuffer(total_size); + for (int i = 0; i != length; i++) { + if (i != 0) { + sb.append(separator); + } + String str = buf[i]; + if (str != null) { + // str == null for undefined or null + sb.append(str); + } + } + return sb.toString(); + } + + /** + * See ECMA 15.4.4.4 + */ + private static Scriptable js_reverse(Context cx, Scriptable thisObj, + Object[] args) + { + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly) { + for (int i=0, j=((int)na.length)-1; i < j; i++,j--) { + Object temp = na.dense[i]; + na.dense[i] = na.dense[j]; + na.dense[j] = temp; + } + return thisObj; + } + } + long len = getLengthProperty(cx, thisObj); + + long half = len / 2; + for(long i=0; i < half; i++) { + long j = len - i - 1; + Object temp1 = getElem(cx, thisObj, i); + Object temp2 = getElem(cx, thisObj, j); + setElem(cx, thisObj, i, temp2); + setElem(cx, thisObj, j, temp1); + } + return thisObj; + } + + /** + * See ECMA 15.4.4.5 + */ + private static Scriptable js_sort(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + long length = getLengthProperty(cx, thisObj); + + if (length <= 1) { return thisObj; } + + Object compare; + Object[] cmpBuf; + + if (args.length > 0 && Undefined.instance != args[0]) { + // sort with given compare function + compare = args[0]; + cmpBuf = new Object[2]; // Buffer for cmp arguments + } else { + // sort with default compare + compare = null; + cmpBuf = null; + } + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly) { + int ilength = (int) length; + heapsort(cx, scope, na.dense, ilength, compare, cmpBuf); + return thisObj; + } + } + + // Should we use the extended sort function, or the faster one? + if (length >= Integer.MAX_VALUE) { + heapsort_extended(cx, scope, thisObj, length, compare, cmpBuf); + } else { + int ilength = (int)length; + // copy the JS array into a working array, so it can be + // sorted cheaply. + Object[] working = new Object[ilength]; + for (int i = 0; i != ilength; ++i) { + working[i] = getElem(cx, thisObj, i); + } + + heapsort(cx, scope, working, ilength, compare, cmpBuf); + + // copy the working array back into thisObj + for (int i = 0; i != ilength; ++i) { + setElem(cx, thisObj, i, working[i]); + } + } + return thisObj; + } + + // Return true only if x > y + private static boolean isBigger(Context cx, Scriptable scope, + Object x, Object y, + Object cmp, Object[] cmpBuf) + { + if (cmp == null) { + if (cmpBuf != null) Kit.codeBug(); + } else { + if (cmpBuf == null || cmpBuf.length != 2) Kit.codeBug(); + } + + Object undef = Undefined.instance; + Object notfound = Scriptable.NOT_FOUND; + + // sort undefined to end + if (y == undef || y == notfound) { + return false; // x can not be bigger then undef + } else if (x == undef || x == notfound) { + return true; // y != undef here, so x > y + } + + if (cmp == null) { + // if no cmp function supplied, sort lexicographically + String a = ScriptRuntime.toString(x); + String b = ScriptRuntime.toString(y); + return a.compareTo(b) > 0; + } + else { + // assemble args and call supplied JS cmp function + cmpBuf[0] = x; + cmpBuf[1] = y; + Callable fun = ScriptRuntime.getValueFunctionAndThis(cmp, cx); + Scriptable funThis = ScriptRuntime.lastStoredScriptable(cx); + + Object ret = fun.call(cx, scope, funThis, cmpBuf); + double d = ScriptRuntime.toNumber(ret); + + // XXX what to do when cmp function returns NaN? ECMA states + // that it's then not a 'consistent comparison function'... but + // then what do we do? Back out and start over with the generic + // cmp function when we see a NaN? Throw an error? + + // for now, just ignore it: + + return d > 0; + } + } + +/** Heapsort implementation. + * See "Introduction to Algorithms" by Cormen, Leiserson, Rivest for details. + * Adjusted for zero based indexes. + */ + private static void heapsort(Context cx, Scriptable scope, + Object[] array, int length, + Object cmp, Object[] cmpBuf) + { + if (length <= 1) Kit.codeBug(); + + // Build heap + for (int i = length / 2; i != 0;) { + --i; + Object pivot = array[i]; + heapify(cx, scope, pivot, array, i, length, cmp, cmpBuf); + } + + // Sort heap + for (int i = length; i != 1;) { + --i; + Object pivot = array[i]; + array[i] = array[0]; + heapify(cx, scope, pivot, array, 0, i, cmp, cmpBuf); + } + } + +/** pivot and child heaps of i should be made into heap starting at i, + * original array[i] is never used to have less array access during sorting. + */ + private static void heapify(Context cx, Scriptable scope, + Object pivot, Object[] array, int i, int end, + Object cmp, Object[] cmpBuf) + { + for (;;) { + int child = i * 2 + 1; + if (child >= end) { + break; + } + Object childVal = array[child]; + if (child + 1 < end) { + Object nextVal = array[child + 1]; + if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) { + ++child; childVal = nextVal; + } + } + if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) { + break; + } + array[i] = childVal; + i = child; + } + array[i] = pivot; + } + +/** Version of heapsort that call getElem/setElem on target to query/assign + * array elements instead of Java array access + */ + private static void heapsort_extended(Context cx, Scriptable scope, + Scriptable target, long length, + Object cmp, Object[] cmpBuf) + { + if (length <= 1) Kit.codeBug(); + + // Build heap + for (long i = length / 2; i != 0;) { + --i; + Object pivot = getElem(cx, target, i); + heapify_extended(cx, scope, pivot, target, i, length, cmp, cmpBuf); + } + + // Sort heap + for (long i = length; i != 1;) { + --i; + Object pivot = getElem(cx, target, i); + setElem(cx, target, i, getElem(cx, target, 0)); + heapify_extended(cx, scope, pivot, target, 0, i, cmp, cmpBuf); + } + } + + private static void heapify_extended(Context cx, Scriptable scope, + Object pivot, Scriptable target, + long i, long end, + Object cmp, Object[] cmpBuf) + { + for (;;) { + long child = i * 2 + 1; + if (child >= end) { + break; + } + Object childVal = getElem(cx, target, child); + if (child + 1 < end) { + Object nextVal = getElem(cx, target, child + 1); + if (isBigger(cx, scope, nextVal, childVal, cmp, cmpBuf)) { + ++child; childVal = nextVal; + } + } + if (!isBigger(cx, scope, childVal, pivot, cmp, cmpBuf)) { + break; + } + setElem(cx, target, i, childVal); + i = child; + } + setElem(cx, target, i, pivot); + } + + /** + * Non-ECMA methods. + */ + + private static Object js_push(Context cx, Scriptable thisObj, + Object[] args) + { + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly && + na.ensureCapacity((int) na.length + args.length)) + { + for (int i = 0; i < args.length; i++) { + na.dense[(int)na.length++] = args[i]; + } + return ScriptRuntime.wrapNumber(na.length); + } + } + long length = getLengthProperty(cx, thisObj); + for (int i = 0; i < args.length; i++) { + setElem(cx, thisObj, length + i, args[i]); + } + + length += args.length; + Object lengthObj = setLengthProperty(cx, thisObj, length); + + /* + * If JS1.2, follow Perl4 by returning the last thing pushed. + * Otherwise, return the new array length. + */ + if (cx.getLanguageVersion() == Context.VERSION_1_2) + // if JS1.2 && no arguments, return undefined. + return args.length == 0 + ? Undefined.instance + : args[args.length - 1]; + + else + return lengthObj; + } + + private static Object js_pop(Context cx, Scriptable thisObj, + Object[] args) + { + Object result; + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly && na.length > 0) { + na.length--; + result = na.dense[(int)na.length]; + na.dense[(int)na.length] = NOT_FOUND; + return result; + } + } + long length = getLengthProperty(cx, thisObj); + if (length > 0) { + length--; + + // Get the to-be-deleted property's value. + result = getElem(cx, thisObj, length); + + // We don't need to delete the last property, because + // setLength does that for us. + } else { + result = Undefined.instance; + } + // necessary to match js even when length < 0; js pop will give a + // length property to any target it is called on. + setLengthProperty(cx, thisObj, length); + + return result; + } + + private static Object js_shift(Context cx, Scriptable thisObj, + Object[] args) + { + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly && na.length > 0) { + na.length--; + Object result = na.dense[0]; + System.arraycopy(na.dense, 1, na.dense, 0, (int)na.length); + na.dense[(int)na.length] = NOT_FOUND; + return result; + } + } + Object result; + long length = getLengthProperty(cx, thisObj); + if (length > 0) { + long i = 0; + length--; + + // Get the to-be-deleted property's value. + result = getElem(cx, thisObj, i); + + /* + * Slide down the array above the first element. Leave i + * set to point to the last element. + */ + if (length > 0) { + for (i = 1; i <= length; i++) { + Object temp = getElem(cx, thisObj, i); + setElem(cx, thisObj, i - 1, temp); + } + } + // We don't need to delete the last property, because + // setLength does that for us. + } else { + result = Undefined.instance; + } + setLengthProperty(cx, thisObj, length); + return result; + } + + private static Object js_unshift(Context cx, Scriptable thisObj, + Object[] args) + { + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly && + na.ensureCapacity((int)na.length + args.length)) + { + System.arraycopy(na.dense, 0, na.dense, args.length, + (int) na.length); + for (int i = 0; i < args.length; i++) { + na.dense[i] = args[i]; + } + na.length += args.length; + return ScriptRuntime.wrapNumber(na.length); + } + } + long length = getLengthProperty(cx, thisObj); + int argc = args.length; + + if (args.length > 0) { + /* Slide up the array to make room for args at the bottom */ + if (length > 0) { + for (long last = length - 1; last >= 0; last--) { + Object temp = getElem(cx, thisObj, last); + setElem(cx, thisObj, last + argc, temp); + } + } + + /* Copy from argv to the bottom of the array. */ + for (int i = 0; i < args.length; i++) { + setElem(cx, thisObj, i, args[i]); + } + + /* Follow Perl by returning the new array length. */ + length += args.length; + return setLengthProperty(cx, thisObj, length); + } + return ScriptRuntime.wrapNumber(length); + } + + private static Object js_splice(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + NativeArray na = null; + boolean denseMode = false; + if (thisObj instanceof NativeArray) { + na = (NativeArray) thisObj; + denseMode = na.denseOnly; + } + + /* create an empty Array to return. */ + scope = getTopLevelScope(scope); + int argc = args.length; + if (argc == 0) + return ScriptRuntime.newObject(cx, scope, "Array", null); + long length = getLengthProperty(cx, thisObj); + + /* Convert the first argument into a starting index. */ + long begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length); + argc--; + + /* Convert the second argument into count */ + long count; + if (args.length == 1) { + count = length - begin; + } else { + double dcount = ScriptRuntime.toInteger(args[1]); + if (dcount < 0) { + count = 0; + } else if (dcount > (length - begin)) { + count = length - begin; + } else { + count = (long)dcount; + } + argc--; + } + + long end = begin + count; + + /* If there are elements to remove, put them into the return value. */ + Object result; + if (count != 0) { + if (count == 1 + && (cx.getLanguageVersion() == Context.VERSION_1_2)) + { + /* + * JS lacks "list context", whereby in Perl one turns the + * single scalar that's spliced out into an array just by + * assigning it to @single instead of $single, or by using it + * as Perl push's first argument, for instance. + * + * JS1.2 emulated Perl too closely and returned a non-Array for + * the single-splice-out case, requiring callers to test and + * wrap in [] if necessary. So JS1.3, default, and other + * versions all return an array of length 1 for uniformity. + */ + result = getElem(cx, thisObj, begin); + } else { + if (denseMode) { + int intLen = (int) (end - begin); + Object[] copy = new Object[intLen]; + System.arraycopy(na.dense, (int) begin, copy, 0, intLen); + result = cx.newArray(scope, copy); + } else { + Scriptable resultArray = ScriptRuntime.newObject(cx, scope, + "Array", null); + for (long last = begin; last != end; last++) { + Object temp = getElem(cx, thisObj, last); + setElem(cx, resultArray, last - begin, temp); + } + result = resultArray; + } + } + } else { // (count == 0) + if (cx.getLanguageVersion() == Context.VERSION_1_2) { + /* Emulate C JS1.2; if no elements are removed, return undefined. */ + result = Undefined.instance; + } else { + result = ScriptRuntime.newObject(cx, scope, "Array", null); + } + } + + /* Find the direction (up or down) to copy and make way for argv. */ + long delta = argc - count; + if (denseMode && length + delta < Integer.MAX_VALUE && + na.ensureCapacity((int) (length + delta))) + { + System.arraycopy(na.dense, (int) end, na.dense, + (int) (begin + argc), (int) (length - end)); + if (argc > 0) { + System.arraycopy(args, 2, na.dense, (int) begin, argc); + } + if (delta < 0) { + Arrays.fill(na.dense, (int) (length + delta), (int) length, + NOT_FOUND); + } + na.length = length + delta; + return result; + } + + if (delta > 0) { + for (long last = length - 1; last >= end; last--) { + Object temp = getElem(cx, thisObj, last); + setElem(cx, thisObj, last + delta, temp); + } + } else if (delta < 0) { + for (long last = end; last < length; last++) { + Object temp = getElem(cx, thisObj, last); + setElem(cx, thisObj, last + delta, temp); + } + } + + /* Copy from argv into the hole to complete the splice. */ + int argoffset = args.length - argc; + for (int i = 0; i < argc; i++) { + setElem(cx, thisObj, begin + i, args[i + argoffset]); + } + + /* Update length in case we deleted elements from the end. */ + setLengthProperty(cx, thisObj, length + delta); + return result; + } + + /* + * See Ecma 262v3 15.4.4.4 + */ + private static Scriptable js_concat(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + // create an empty Array to return. + scope = getTopLevelScope(scope); + Function ctor = ScriptRuntime.getExistingCtor(cx, scope, "Array"); + Scriptable result = ctor.construct(cx, scope, ScriptRuntime.emptyArgs); + if (thisObj instanceof NativeArray && result instanceof NativeArray) { + NativeArray denseThis = (NativeArray) thisObj; + NativeArray denseResult = (NativeArray) result; + if (denseThis.denseOnly && denseResult.denseOnly) { + // First calculate length of resulting array + boolean canUseDense = true; + int length = (int) denseThis.length; + for (int i = 0; i < args.length && canUseDense; i++) { + if (ScriptRuntime.instanceOf(args[i], ctor, cx)) { + // only try to use dense approach for Array-like + // objects that are actually NativeArrays + canUseDense = args[i] instanceof NativeArray; + length += ((NativeArray) args[i]).length; + } else { + length++; + } + } + if (canUseDense && denseResult.ensureCapacity(length)) { + System.arraycopy(denseThis.dense, 0, denseResult.dense, + 0, (int) denseThis.length); + int cursor = (int) denseThis.length; + for (int i = 0; i < args.length && canUseDense; i++) { + if (args[i] instanceof NativeArray) { + NativeArray arg = (NativeArray) args[i]; + System.arraycopy(arg.dense, 0, + denseResult.dense, cursor, + (int)arg.length); + cursor += (int)arg.length; + } else { + denseResult.dense[cursor++] = args[i]; + } + } + denseResult.length = length; + return result; + } + } + } + + long length; + long slot = 0; + + /* Put the target in the result array; only add it as an array + * if it looks like one. + */ + if (ScriptRuntime.instanceOf(thisObj, ctor, cx)) { + length = getLengthProperty(cx, thisObj); + + // Copy from the target object into the result + for (slot = 0; slot < length; slot++) { + Object temp = getElem(cx, thisObj, slot); + setElem(cx, result, slot, temp); + } + } else { + setElem(cx, result, slot++, thisObj); + } + + /* Copy from the arguments into the result. If any argument + * has a numeric length property, treat it as an array and add + * elements separately; otherwise, just copy the argument. + */ + for (int i = 0; i < args.length; i++) { + if (ScriptRuntime.instanceOf(args[i], ctor, cx)) { + // ScriptRuntime.instanceOf => instanceof Scriptable + Scriptable arg = (Scriptable)args[i]; + length = getLengthProperty(cx, arg); + for (long j = 0; j < length; j++, slot++) { + Object temp = getElem(cx, arg, j); + setElem(cx, result, slot, temp); + } + } else { + setElem(cx, result, slot++, args[i]); + } + } + return result; + } + + private Scriptable js_slice(Context cx, Scriptable thisObj, + Object[] args) + { + Scriptable scope = getTopLevelScope(this); + Scriptable result = ScriptRuntime.newObject(cx, scope, "Array", null); + long length = getLengthProperty(cx, thisObj); + + long begin, end; + if (args.length == 0) { + begin = 0; + end = length; + } else { + begin = toSliceIndex(ScriptRuntime.toInteger(args[0]), length); + if (args.length == 1) { + end = length; + } else { + end = toSliceIndex(ScriptRuntime.toInteger(args[1]), length); + } + } + + for (long slot = begin; slot < end; slot++) { + Object temp = getElem(cx, thisObj, slot); + setElem(cx, result, slot - begin, temp); + } + + return result; + } + + private static long toSliceIndex(double value, long length) { + long result; + if (value < 0.0) { + if (value + length < 0.0) { + result = 0; + } else { + result = (long)(value + length); + } + } else if (value > length) { + result = length; + } else { + result = (long)value; + } + return result; + } + + /** + * Implements the methods "indexOf" and "lastIndexOf". + */ + private Object indexOfHelper(Context cx, Scriptable thisObj, + Object[] args, boolean isLast) + { + Object compareTo = args.length > 0 ? args[0] : Undefined.instance; + long length = getLengthProperty(cx, thisObj); + long start; + if (isLast) { + // lastIndexOf + /* + * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf + * The index at which to start searching backwards. Defaults to the + * array's length, i.e. the whole array will be searched. If the + * index is greater than or equal to the length of the array, the + * whole array will be searched. If negative, it is taken as the + * offset from the end of the array. Note that even when the index + * is negative, the array is still searched from back to front. If + * the calculated index is less than 0, -1 is returned, i.e. the + * array will not be searched. + */ + if (args.length < 2) { + // default + start = length-1; + } else { + start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1])); + if (start >= length) + start = length-1; + else if (start < 0) + start += length; + // Note that start may be negative, but that's okay + // as the result of -1 will fall out from the code below + } + } else { + // indexOf + /* + * From http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:indexOf + * The index at which to begin the search. Defaults to 0, i.e. the + * whole array will be searched. If the index is greater than or + * equal to the length of the array, -1 is returned, i.e. the array + * will not be searched. If negative, it is taken as the offset from + * the end of the array. Note that even when the index is negative, + * the array is still searched from front to back. If the calculated + * index is less than 0, the whole array will be searched. + */ + if (args.length < 2) { + // default + start = 0; + } else { + start = ScriptRuntime.toInt32(ScriptRuntime.toNumber(args[1])); + if (start < 0) { + start += length; + if (start < 0) + start = 0; + } + // Note that start may be > length-1, but that's okay + // as the result of -1 will fall out from the code below + } + } + if (thisObj instanceof NativeArray) { + NativeArray na = (NativeArray) thisObj; + if (na.denseOnly) { + if (isLast) { + for (int i=(int)start; i >= 0; i--) { + if (na.dense[i] != Scriptable.NOT_FOUND && + ScriptRuntime.shallowEq(na.dense[i], compareTo)) + { + return new Long(i); + } + } + } else { + for (int i=(int)start; i < length; i++) { + if (na.dense[i] != Scriptable.NOT_FOUND && + ScriptRuntime.shallowEq(na.dense[i], compareTo)) + { + return new Long(i); + } + } + } + return NEGATIVE_ONE; + } + } + if (isLast) { + for (long i=start; i >= 0; i--) { + if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) { + return new Long(i); + } + } + } else { + for (long i=start; i < length; i++) { + if (ScriptRuntime.shallowEq(getElem(cx, thisObj, i), compareTo)) { + return new Long(i); + } + } + } + return NEGATIVE_ONE; + } + + /** + * Implements the methods "every", "filter", "forEach", "map", and "some". + */ + private Object iterativeMethod(Context cx, int id, Scriptable scope, + Scriptable thisObj, Object[] args) + { + Object callbackArg = args.length > 0 ? args[0] : Undefined.instance; + if (callbackArg == null || !(callbackArg instanceof Function)) { + throw ScriptRuntime.notFunctionError( + ScriptRuntime.toString(callbackArg)); + } + Function f = (Function) callbackArg; + Scriptable parent = ScriptableObject.getTopLevelScope(f); + Scriptable thisArg; + if (args.length < 2 || args[1] == null || args[1] == Undefined.instance) + { + thisArg = parent; + } else { + thisArg = ScriptRuntime.toObject(cx, scope, args[1]); + } + long length = getLengthProperty(cx, thisObj); + Scriptable array = ScriptRuntime.newObject(cx, scope, "Array", null); + long j=0; + for (long i=0; i < length; i++) { + Object[] innerArgs = new Object[3]; + Object elem = (i > Integer.MAX_VALUE) + ? ScriptableObject.getProperty(thisObj, Long.toString(i)) + : ScriptableObject.getProperty(thisObj, (int)i); + if (elem == Scriptable.NOT_FOUND) { + continue; + } + innerArgs[0] = elem; + innerArgs[1] = new Long(i); + innerArgs[2] = thisObj; + Object result = f.call(cx, parent, thisArg, innerArgs); + switch (id) { + case Id_every: + if (!ScriptRuntime.toBoolean(result)) + return Boolean.FALSE; + break; + case Id_filter: + if (ScriptRuntime.toBoolean(result)) + setElem(cx, array, j++, innerArgs[0]); + break; + case Id_forEach: + break; + case Id_map: + setElem(cx, array, i, result); + break; + case Id_some: + if (ScriptRuntime.toBoolean(result)) + return Boolean.TRUE; + break; + } + } + switch (id) { + case Id_every: + return Boolean.TRUE; + case Id_filter: + case Id_map: + return array; + case Id_some: + return Boolean.FALSE; + case Id_forEach: + default: + return Undefined.instance; + } + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2005-09-26 15:47:42 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 3: c=s.charAt(0); + if (c=='m') { if (s.charAt(2)=='p' && s.charAt(1)=='a') {id=Id_map; break L0;} } + else if (c=='p') { if (s.charAt(2)=='p' && s.charAt(1)=='o') {id=Id_pop; break L0;} } + break L; + case 4: switch (s.charAt(2)) { + case 'i': X="join";id=Id_join; break L; + case 'm': X="some";id=Id_some; break L; + case 'r': X="sort";id=Id_sort; break L; + case 's': X="push";id=Id_push; break L; + } break L; + case 5: c=s.charAt(1); + if (c=='h') { X="shift";id=Id_shift; } + else if (c=='l') { X="slice";id=Id_slice; } + else if (c=='v') { X="every";id=Id_every; } + break L; + case 6: c=s.charAt(0); + if (c=='c') { X="concat";id=Id_concat; } + else if (c=='f') { X="filter";id=Id_filter; } + else if (c=='s') { X="splice";id=Id_splice; } + break L; + case 7: switch (s.charAt(0)) { + case 'f': X="forEach";id=Id_forEach; break L; + case 'i': X="indexOf";id=Id_indexOf; break L; + case 'r': X="reverse";id=Id_reverse; break L; + case 'u': X="unshift";id=Id_unshift; break L; + } break L; + case 8: c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + case 11: c=s.charAt(0); + if (c=='c') { X="constructor";id=Id_constructor; } + else if (c=='l') { X="lastIndexOf";id=Id_lastIndexOf; } + break L; + case 14: X="toLocaleString";id=Id_toLocaleString; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toLocaleString = 3, + Id_toSource = 4, + Id_join = 5, + Id_reverse = 6, + Id_sort = 7, + Id_push = 8, + Id_pop = 9, + Id_shift = 10, + Id_unshift = 11, + Id_splice = 12, + Id_concat = 13, + Id_slice = 14, + Id_indexOf = 15, + Id_lastIndexOf = 16, + Id_every = 17, + Id_filter = 18, + Id_forEach = 19, + Id_map = 20, + Id_some = 21, + + MAX_PROTOTYPE_ID = 21; + +// #/string_id_map# + + private static final int + ConstructorId_join = -Id_join, + ConstructorId_reverse = -Id_reverse, + ConstructorId_sort = -Id_sort, + ConstructorId_push = -Id_push, + ConstructorId_pop = -Id_pop, + ConstructorId_shift = -Id_shift, + ConstructorId_unshift = -Id_unshift, + ConstructorId_splice = -Id_splice, + ConstructorId_concat = -Id_concat, + ConstructorId_slice = -Id_slice, + ConstructorId_indexOf = -Id_indexOf, + ConstructorId_lastIndexOf = -Id_lastIndexOf, + ConstructorId_every = -Id_every, + ConstructorId_filter = -Id_filter, + ConstructorId_forEach = -Id_forEach, + ConstructorId_map = -Id_map, + ConstructorId_some = -Id_some; + + /** + * Internal representation of the JavaScript array's length property. + */ + private long length; + + /** + * Fast storage for dense arrays. Sparse arrays will use the superclass's + * hashtable storage scheme. + */ + private Object[] dense; + + /** + * True if all numeric properties are stored in <code>dense</code>. + */ + private boolean denseOnly; + + /** + * The maximum size of <code>dense</code> that will be allocated initially. + */ + private static int maximumInitialCapacity = 10000; + + /** + * The default capacity for <code>dense</code>. + */ + private static final int DEFAULT_INITIAL_CAPACITY = 10; + + /** + * The factor to grow <code>dense</code> by. + */ + private static final double GROW_FACTOR = 1.5; + private static final int MAX_PRE_GROW_SIZE = (int)(Integer.MAX_VALUE / GROW_FACTOR); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeBoolean.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeBoolean.java new file mode 100644 index 0000000..b6a106a --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeBoolean.java @@ -0,0 +1,170 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * 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; + +/** + * This class implements the Boolean native object. + * See ECMA 15.6. + * @author Norris Boyd + */ +final class NativeBoolean extends IdScriptableObject +{ + static final long serialVersionUID = -3716996899943880933L; + + private static final Object BOOLEAN_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeBoolean obj = new NativeBoolean(false); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + private NativeBoolean(boolean b) + { + booleanValue = b; + } + + public String getClassName() + { + return "Boolean"; + } + + public Object getDefaultValue(Class typeHint) { + // This is actually non-ECMA, but will be proposed + // as a change in round 2. + if (typeHint == ScriptRuntime.BooleanClass) + return ScriptRuntime.wrapBoolean(booleanValue); + return super.getDefaultValue(typeHint); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_valueOf: arity=0; s="valueOf"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(BOOLEAN_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(BOOLEAN_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + + if (id == Id_constructor) { + boolean b; + if (args.length == 0) { + b = false; + } else { + b = args[0] instanceof ScriptableObject && + ((ScriptableObject) args[0]).avoidObjectDetection() + ? true + : ScriptRuntime.toBoolean(args[0]); + } + if (thisObj == null) { + // new Boolean(val) creates a new boolean object. + return new NativeBoolean(b); + } + // Boolean(val) converts val to a boolean. + return ScriptRuntime.wrapBoolean(b); + } + + // The rest of Boolean.prototype methods require thisObj to be Boolean + + if (!(thisObj instanceof NativeBoolean)) + throw incompatibleCallError(f); + boolean value = ((NativeBoolean)thisObj).booleanValue; + + switch (id) { + + case Id_toString: + return value ? "true" : "false"; + + case Id_toSource: + return value ? "(new Boolean(true))" : "(new Boolean(false))"; + + case Id_valueOf: + return ScriptRuntime.wrapBoolean(value); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:31 EDT + L0: { id = 0; String X = null; int c; + int s_length = s.length(); + if (s_length==7) { X="valueOf";id=Id_valueOf; } + else if (s_length==8) { + c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + } + else if (s_length==11) { X="constructor";id=Id_constructor; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toSource = 3, + Id_valueOf = 4, + MAX_PROTOTYPE_ID = 4; + +// #/string_id_map# + + private boolean booleanValue; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeCall.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeCall.java new file mode 100644 index 0000000..b196ac3 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeCall.java @@ -0,0 +1,154 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Bob Jervis + * + * 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; + +/** + * This class implements the activation object. + * + * See ECMA 10.1.6 + * + * @see org.mozilla.javascript.Arguments + * @author Norris Boyd + */ +public final class NativeCall extends IdScriptableObject +{ + static final long serialVersionUID = -7471457301304454454L; + + private static final Object CALL_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeCall obj = new NativeCall(); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + NativeCall() { } + + NativeCall(NativeFunction function, Scriptable scope, Object[] args) + { + this.function = function; + + setParentScope(scope); + // leave prototype null + + this.originalArgs = (args == null) ? ScriptRuntime.emptyArgs : args; + + // initialize values of arguments + int paramAndVarCount = function.getParamAndVarCount(); + int paramCount = function.getParamCount(); + if (paramAndVarCount != 0) { + for (int i = 0; i < paramCount; ++i) { + String name = function.getParamOrVarName(i); + Object val = i < args.length ? args[i] + : Undefined.instance; + defineProperty(name, val, PERMANENT); + } + } + + // initialize "arguments" property but only if it was not overridden by + // the parameter with the same name + if (!super.has("arguments", this)) { + defineProperty("arguments", new Arguments(this), PERMANENT); + } + + if (paramAndVarCount != 0) { + for (int i = paramCount; i < paramAndVarCount; ++i) { + String name = function.getParamOrVarName(i); + if (!super.has(name, this)) { + if (function.getParamOrVarConst(i)) + defineProperty(name, Undefined.instance, CONST); + else + defineProperty(name, Undefined.instance, PERMANENT); + } + } + } + } + + public String getClassName() + { + return "Call"; + } + + protected int findPrototypeId(String s) + { + return s.equals("constructor") ? Id_constructor : 0; + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + if (id == Id_constructor) { + arity=1; s="constructor"; + } else { + throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(CALL_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(CALL_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + if (id == Id_constructor) { + if (thisObj != null) { + throw Context.reportRuntimeError1("msg.only.from.new", "Call"); + } + ScriptRuntime.checkDeprecated(cx, "Call"); + NativeCall result = new NativeCall(); + result.setPrototype(getObjectPrototype(scope)); + return result; + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private static final int + Id_constructor = 1, + MAX_PROTOTYPE_ID = 1; + + NativeFunction function; + Object[] originalArgs; + + transient NativeCall parentActivationCall; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeDate.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeDate.java new file mode 100644 index 0000000..75d41ab --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeDate.java @@ -0,0 +1,1604 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Peter Annema + * Norris Boyd + * Mike McCabe + * Ilya Frank + * + * + * 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.util.Date; +import java.text.DateFormat; + +/** + * This class implements the Date native object. + * See ECMA 15.9. + * @author Mike McCabe + */ +final class NativeDate extends IdScriptableObject +{ + static final long serialVersionUID = -8307438915861678966L; + + private static final Object DATE_TAG = new Object(); + + private static final String js_NaN_date_str = "Invalid Date"; + + static void init(Scriptable scope, boolean sealed) + { + NativeDate obj = new NativeDate(); + // Set the value of the prototype Date to NaN ('invalid date'); + obj.date = ScriptRuntime.NaN; + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + private NativeDate() + { + if (thisTimeZone == null) { + // j.u.TimeZone is synchronized, so setting class statics from it + // should be OK. + thisTimeZone = java.util.TimeZone.getDefault(); + LocalTZA = thisTimeZone.getRawOffset(); + } + } + + public String getClassName() + { + return "Date"; + } + + public Object getDefaultValue(Class typeHint) + { + if (typeHint == null) + typeHint = ScriptRuntime.StringClass; + return super.getDefaultValue(typeHint); + } + + double getJSTimeValue() + { + return date; + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_now, + "now", 0); + addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_parse, + "parse", 1); + addIdFunctionProperty(ctor, DATE_TAG, ConstructorId_UTC, + "UTC", 1); + super.fillConstructorProperties(ctor); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toTimeString: arity=0; s="toTimeString"; break; + case Id_toDateString: arity=0; s="toDateString"; break; + case Id_toLocaleString: arity=0; s="toLocaleString"; break; + case Id_toLocaleTimeString: arity=0; s="toLocaleTimeString"; break; + case Id_toLocaleDateString: arity=0; s="toLocaleDateString"; break; + case Id_toUTCString: arity=0; s="toUTCString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_valueOf: arity=0; s="valueOf"; break; + case Id_getTime: arity=0; s="getTime"; break; + case Id_getYear: arity=0; s="getYear"; break; + case Id_getFullYear: arity=0; s="getFullYear"; break; + case Id_getUTCFullYear: arity=0; s="getUTCFullYear"; break; + case Id_getMonth: arity=0; s="getMonth"; break; + case Id_getUTCMonth: arity=0; s="getUTCMonth"; break; + case Id_getDate: arity=0; s="getDate"; break; + case Id_getUTCDate: arity=0; s="getUTCDate"; break; + case Id_getDay: arity=0; s="getDay"; break; + case Id_getUTCDay: arity=0; s="getUTCDay"; break; + case Id_getHours: arity=0; s="getHours"; break; + case Id_getUTCHours: arity=0; s="getUTCHours"; break; + case Id_getMinutes: arity=0; s="getMinutes"; break; + case Id_getUTCMinutes: arity=0; s="getUTCMinutes"; break; + case Id_getSeconds: arity=0; s="getSeconds"; break; + case Id_getUTCSeconds: arity=0; s="getUTCSeconds"; break; + case Id_getMilliseconds: arity=0; s="getMilliseconds"; break; + case Id_getUTCMilliseconds: arity=0; s="getUTCMilliseconds"; break; + case Id_getTimezoneOffset: arity=0; s="getTimezoneOffset"; break; + case Id_setTime: arity=1; s="setTime"; break; + case Id_setMilliseconds: arity=1; s="setMilliseconds"; break; + case Id_setUTCMilliseconds: arity=1; s="setUTCMilliseconds"; break; + case Id_setSeconds: arity=2; s="setSeconds"; break; + case Id_setUTCSeconds: arity=2; s="setUTCSeconds"; break; + case Id_setMinutes: arity=3; s="setMinutes"; break; + case Id_setUTCMinutes: arity=3; s="setUTCMinutes"; break; + case Id_setHours: arity=4; s="setHours"; break; + case Id_setUTCHours: arity=4; s="setUTCHours"; break; + case Id_setDate: arity=1; s="setDate"; break; + case Id_setUTCDate: arity=1; s="setUTCDate"; break; + case Id_setMonth: arity=2; s="setMonth"; break; + case Id_setUTCMonth: arity=2; s="setUTCMonth"; break; + case Id_setFullYear: arity=3; s="setFullYear"; break; + case Id_setUTCFullYear: arity=3; s="setUTCFullYear"; break; + case Id_setYear: arity=1; s="setYear"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(DATE_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(DATE_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case ConstructorId_now: + return ScriptRuntime.wrapNumber(now()); + + case ConstructorId_parse: + { + String dataStr = ScriptRuntime.toString(args, 0); + return ScriptRuntime.wrapNumber(date_parseString(dataStr)); + } + + case ConstructorId_UTC: + return ScriptRuntime.wrapNumber(jsStaticFunction_UTC(args)); + + case Id_constructor: + { + // if called as a function, just return a string + // representing the current time. + if (thisObj != null) + return date_format(now(), Id_toString); + return jsConstructor(args); + } + } + + // The rest of Date.prototype methods require thisObj to be Date + + if (!(thisObj instanceof NativeDate)) + throw incompatibleCallError(f); + NativeDate realThis = (NativeDate)thisObj; + double t = realThis.date; + + switch (id) { + + case Id_toString: + case Id_toTimeString: + case Id_toDateString: + if (t == t) { + return date_format(t, id); + } + return js_NaN_date_str; + + case Id_toLocaleString: + case Id_toLocaleTimeString: + case Id_toLocaleDateString: + if (t == t) { + return toLocale_helper(t, id); + } + return js_NaN_date_str; + + case Id_toUTCString: + if (t == t) { + return js_toUTCString(t); + } + return js_NaN_date_str; + + case Id_toSource: + return "(new Date("+ScriptRuntime.toString(t)+"))"; + + case Id_valueOf: + case Id_getTime: + return ScriptRuntime.wrapNumber(t); + + case Id_getYear: + case Id_getFullYear: + case Id_getUTCFullYear: + if (t == t) { + if (id != Id_getUTCFullYear) t = LocalTime(t); + t = YearFromTime(t); + if (id == Id_getYear) { + if (cx.hasFeature(Context.FEATURE_NON_ECMA_GET_YEAR)) { + if (1900 <= t && t < 2000) { + t -= 1900; + } + } else { + t -= 1900; + } + } + } + return ScriptRuntime.wrapNumber(t); + + case Id_getMonth: + case Id_getUTCMonth: + if (t == t) { + if (id == Id_getMonth) t = LocalTime(t); + t = MonthFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getDate: + case Id_getUTCDate: + if (t == t) { + if (id == Id_getDate) t = LocalTime(t); + t = DateFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getDay: + case Id_getUTCDay: + if (t == t) { + if (id == Id_getDay) t = LocalTime(t); + t = WeekDay(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getHours: + case Id_getUTCHours: + if (t == t) { + if (id == Id_getHours) t = LocalTime(t); + t = HourFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getMinutes: + case Id_getUTCMinutes: + if (t == t) { + if (id == Id_getMinutes) t = LocalTime(t); + t = MinFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getSeconds: + case Id_getUTCSeconds: + if (t == t) { + if (id == Id_getSeconds) t = LocalTime(t); + t = SecFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getMilliseconds: + case Id_getUTCMilliseconds: + if (t == t) { + if (id == Id_getMilliseconds) t = LocalTime(t); + t = msFromTime(t); + } + return ScriptRuntime.wrapNumber(t); + + case Id_getTimezoneOffset: + if (t == t) { + t = (t - LocalTime(t)) / msPerMinute; + } + return ScriptRuntime.wrapNumber(t); + + case Id_setTime: + t = TimeClip(ScriptRuntime.toNumber(args, 0)); + realThis.date = t; + return ScriptRuntime.wrapNumber(t); + + case Id_setMilliseconds: + case Id_setUTCMilliseconds: + case Id_setSeconds: + case Id_setUTCSeconds: + case Id_setMinutes: + case Id_setUTCMinutes: + case Id_setHours: + case Id_setUTCHours: + t = makeTime(t, args, id); + realThis.date = t; + return ScriptRuntime.wrapNumber(t); + + case Id_setDate: + case Id_setUTCDate: + case Id_setMonth: + case Id_setUTCMonth: + case Id_setFullYear: + case Id_setUTCFullYear: + t = makeDate(t, args, id); + realThis.date = t; + return ScriptRuntime.wrapNumber(t); + + case Id_setYear: + { + double year = ScriptRuntime.toNumber(args, 0); + + if (year != year || Double.isInfinite(year)) { + t = ScriptRuntime.NaN; + } else { + if (t != t) { + t = 0; + } else { + t = LocalTime(t); + } + + if (year >= 0 && year <= 99) + year += 1900; + + double day = MakeDay(year, MonthFromTime(t), + DateFromTime(t)); + t = MakeDate(day, TimeWithinDay(t)); + t = internalUTC(t); + t = TimeClip(t); + } + } + realThis.date = t; + return ScriptRuntime.wrapNumber(t); + + default: throw new IllegalArgumentException(String.valueOf(id)); + } + + } + + /* ECMA helper functions */ + + private static final double HalfTimeDomain = 8.64e15; + private static final double HoursPerDay = 24.0; + private static final double MinutesPerHour = 60.0; + private static final double SecondsPerMinute = 60.0; + private static final double msPerSecond = 1000.0; + private static final double MinutesPerDay = (HoursPerDay * MinutesPerHour); + private static final double SecondsPerDay = (MinutesPerDay * SecondsPerMinute); + private static final double SecondsPerHour = (MinutesPerHour * SecondsPerMinute); + private static final double msPerDay = (SecondsPerDay * msPerSecond); + private static final double msPerHour = (SecondsPerHour * msPerSecond); + private static final double msPerMinute = (SecondsPerMinute * msPerSecond); + + private static double Day(double t) + { + return Math.floor(t / msPerDay); + } + + private static double TimeWithinDay(double t) + { + double result; + result = t % msPerDay; + if (result < 0) + result += msPerDay; + return result; + } + + private static boolean IsLeapYear(int year) + { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + /* math here has to be f.p, because we need + * floor((1968 - 1969) / 4) == -1 + */ + private static double DayFromYear(double y) + { + return ((365 * ((y)-1970) + Math.floor(((y)-1969)/4.0) + - Math.floor(((y)-1901)/100.0) + Math.floor(((y)-1601)/400.0))); + } + + private static double TimeFromYear(double y) + { + return DayFromYear(y) * msPerDay; + } + + private static int YearFromTime(double t) + { + int lo = (int) Math.floor((t / msPerDay) / 366) + 1970; + int hi = (int) Math.floor((t / msPerDay) / 365) + 1970; + int mid; + + /* above doesn't work for negative dates... */ + if (hi < lo) { + int temp = lo; + lo = hi; + hi = temp; + } + + /* Use a simple binary search algorithm to find the right + year. This seems like brute force... but the computation + of hi and lo years above lands within one year of the + correct answer for years within a thousand years of + 1970; the loop below only requires six iterations + for year 270000. */ + while (hi > lo) { + mid = (hi + lo) / 2; + if (TimeFromYear(mid) > t) { + hi = mid - 1; + } else { + lo = mid + 1; + if (TimeFromYear(lo) > t) { + return mid; + } + } + } + return lo; + } + + private static double DayFromMonth(int m, int year) + { + int day = m * 30; + + if (m >= 7) { day += m / 2 - 1; } + else if (m >= 2) { day += (m - 1) / 2 - 1; } + else { day += m; } + + if (m >= 2 && IsLeapYear(year)) { ++day; } + + return day; + } + + private static int MonthFromTime(double t) + { + int year = YearFromTime(t); + int d = (int)(Day(t) - DayFromYear(year)); + + d -= 31 + 28; + if (d < 0) { + return (d < -28) ? 0 : 1; + } + + if (IsLeapYear(year)) { + if (d == 0) + return 1; // 29 February + --d; + } + + // d: date count from 1 March + int estimate = d / 30; // approx number of month since March + int mstart; + switch (estimate) { + case 0: return 2; + case 1: mstart = 31; break; + case 2: mstart = 31+30; break; + case 3: mstart = 31+30+31; break; + case 4: mstart = 31+30+31+30; break; + case 5: mstart = 31+30+31+30+31; break; + case 6: mstart = 31+30+31+30+31+31; break; + case 7: mstart = 31+30+31+30+31+31+30; break; + case 8: mstart = 31+30+31+30+31+31+30+31; break; + case 9: mstart = 31+30+31+30+31+31+30+31+30; break; + case 10: return 11; //Late december + default: throw Kit.codeBug(); + } + // if d < mstart then real month since March == estimate - 1 + return (d >= mstart) ? estimate + 2 : estimate + 1; + } + + private static int DateFromTime(double t) + { + int year = YearFromTime(t); + int d = (int)(Day(t) - DayFromYear(year)); + + d -= 31 + 28; + if (d < 0) { + return (d < -28) ? d + 31 + 28 + 1 : d + 28 + 1; + } + + if (IsLeapYear(year)) { + if (d == 0) + return 29; // 29 February + --d; + } + + // d: date count from 1 March + int mdays, mstart; + switch (d / 30) { // approx number of month since March + case 0: return d + 1; + case 1: mdays = 31; mstart = 31; break; + case 2: mdays = 30; mstart = 31+30; break; + case 3: mdays = 31; mstart = 31+30+31; break; + case 4: mdays = 30; mstart = 31+30+31+30; break; + case 5: mdays = 31; mstart = 31+30+31+30+31; break; + case 6: mdays = 31; mstart = 31+30+31+30+31+31; break; + case 7: mdays = 30; mstart = 31+30+31+30+31+31+30; break; + case 8: mdays = 31; mstart = 31+30+31+30+31+31+30+31; break; + case 9: mdays = 30; mstart = 31+30+31+30+31+31+30+31+30; break; + case 10: + return d - (31+30+31+30+31+31+30+31+30) + 1; //Late december + default: throw Kit.codeBug(); + } + d -= mstart; + if (d < 0) { + // wrong estimate: sfhift to previous month + d += mdays; + } + return d + 1; + } + + private static int WeekDay(double t) + { + double result; + result = Day(t) + 4; + result = result % 7; + if (result < 0) + result += 7; + return (int) result; + } + + private static double now() + { + return System.currentTimeMillis(); + } + + /* Should be possible to determine the need for this dynamically + * if we go with the workaround... I'm not using it now, because I + * can't think of any clean way to make toLocaleString() and the + * time zone (comment) in toString match the generated string + * values. Currently it's wrong-but-consistent in all but the + * most recent betas of the JRE - seems to work in 1.1.7. + */ + private final static boolean TZO_WORKAROUND = false; + private static double DaylightSavingTA(double t) + { + // Another workaround! The JRE doesn't seem to know about DST + // before year 1 AD, so we map to equivalent dates for the + // purposes of finding dst. To be safe, we do this for years + // outside 1970-2038. + if (t < 0.0 || t > 2145916800000.0) { + int year = EquivalentYear(YearFromTime(t)); + double day = MakeDay(year, MonthFromTime(t), DateFromTime(t)); + t = MakeDate(day, TimeWithinDay(t)); + } + if (!TZO_WORKAROUND) { + Date date = new Date((long) t); + if (thisTimeZone.inDaylightTime(date)) + return msPerHour; + else + return 0; + } else { + /* Use getOffset if inDaylightTime() is broken, because it + * seems to work acceptably. We don't switch over to it + * entirely, because it requires (expensive) exploded date arguments, + * and the api makes it impossible to handle dst + * changeovers cleanly. + */ + + // Hardcode the assumption that the changeover always + // happens at 2:00 AM: + t += LocalTZA + (HourFromTime(t) <= 2 ? msPerHour : 0); + + int year = YearFromTime(t); + double offset = thisTimeZone.getOffset(year > 0 ? 1 : 0, + year, + MonthFromTime(t), + DateFromTime(t), + WeekDay(t), + (int)TimeWithinDay(t)); + + if ((offset - LocalTZA) != 0) + return msPerHour; + else + return 0; + // return offset - LocalTZA; + } + } + + /* + * Find a year for which any given date will fall on the same weekday. + * + * This function should be used with caution when used other than + * for determining DST; it hasn't been proven not to produce an + * incorrect year for times near year boundaries. + */ + private static int EquivalentYear(int year) + { + int day = (int) DayFromYear(year) + 4; + day = day % 7; + if (day < 0) + day += 7; + // Years and leap years on which Jan 1 is a Sunday, Monday, etc. + if (IsLeapYear(year)) { + switch (day) { + case 0: return 1984; + case 1: return 1996; + case 2: return 1980; + case 3: return 1992; + case 4: return 1976; + case 5: return 1988; + case 6: return 1972; + } + } else { + switch (day) { + case 0: return 1978; + case 1: return 1973; + case 2: return 1974; + case 3: return 1975; + case 4: return 1981; + case 5: return 1971; + case 6: return 1977; + } + } + // Unreachable + throw Kit.codeBug(); + } + + private static double LocalTime(double t) + { + return t + LocalTZA + DaylightSavingTA(t); + } + + private static double internalUTC(double t) + { + return t - LocalTZA - DaylightSavingTA(t - LocalTZA); + } + + private static int HourFromTime(double t) + { + double result; + result = Math.floor(t / msPerHour) % HoursPerDay; + if (result < 0) + result += HoursPerDay; + return (int) result; + } + + private static int MinFromTime(double t) + { + double result; + result = Math.floor(t / msPerMinute) % MinutesPerHour; + if (result < 0) + result += MinutesPerHour; + return (int) result; + } + + private static int SecFromTime(double t) + { + double result; + result = Math.floor(t / msPerSecond) % SecondsPerMinute; + if (result < 0) + result += SecondsPerMinute; + return (int) result; + } + + private static int msFromTime(double t) + { + double result; + result = t % msPerSecond; + if (result < 0) + result += msPerSecond; + return (int) result; + } + + private static double MakeTime(double hour, double min, + double sec, double ms) + { + return ((hour * MinutesPerHour + min) * SecondsPerMinute + sec) + * msPerSecond + ms; + } + + private static double MakeDay(double year, double month, double date) + { + year += Math.floor(month / 12); + + month = month % 12; + if (month < 0) + month += 12; + + double yearday = Math.floor(TimeFromYear(year) / msPerDay); + double monthday = DayFromMonth((int)month, (int)year); + + return yearday + monthday + date - 1; + } + + private static double MakeDate(double day, double time) + { + return day * msPerDay + time; + } + + private static double TimeClip(double d) + { + if (d != d || + d == Double.POSITIVE_INFINITY || + d == Double.NEGATIVE_INFINITY || + Math.abs(d) > HalfTimeDomain) + { + return ScriptRuntime.NaN; + } + if (d > 0.0) + return Math.floor(d + 0.); + else + return Math.ceil(d + 0.); + } + + /* end of ECMA helper functions */ + + /* find UTC time from given date... no 1900 correction! */ + private static double date_msecFromDate(double year, double mon, + double mday, double hour, + double min, double sec, + double msec) + { + double day; + double time; + double result; + + day = MakeDay(year, mon, mday); + time = MakeTime(hour, min, sec, msec); + result = MakeDate(day, time); + return result; + } + + /* compute the time in msec (unclipped) from the given args */ + private static final int MAXARGS = 7; + private static double date_msecFromArgs(Object[] args) + { + double array[] = new double[MAXARGS]; + int loop; + double d; + + for (loop = 0; loop < MAXARGS; loop++) { + if (loop < args.length) { + d = ScriptRuntime.toNumber(args[loop]); + if (d != d || Double.isInfinite(d)) { + return ScriptRuntime.NaN; + } + array[loop] = ScriptRuntime.toInteger(args[loop]); + } else { + if (loop == 2) { + array[loop] = 1; /* Default the date argument to 1. */ + } else { + array[loop] = 0; + } + } + } + + /* adjust 2-digit years into the 20th century */ + if (array[0] >= 0 && array[0] <= 99) + array[0] += 1900; + + return date_msecFromDate(array[0], array[1], array[2], + array[3], array[4], array[5], array[6]); + } + + private static double jsStaticFunction_UTC(Object[] args) + { + return TimeClip(date_msecFromArgs(args)); + } + + private static double date_parseString(String s) + { + int year = -1; + int mon = -1; + int mday = -1; + int hour = -1; + int min = -1; + int sec = -1; + char c = 0; + char si = 0; + int i = 0; + int n = -1; + double tzoffset = -1; + char prevc = 0; + int limit = 0; + boolean seenplusminus = false; + + limit = s.length(); + while (i < limit) { + c = s.charAt(i); + i++; + if (c <= ' ' || c == ',' || c == '-') { + if (i < limit) { + si = s.charAt(i); + if (c == '-' && '0' <= si && si <= '9') { + prevc = c; + } + } + continue; + } + if (c == '(') { /* comments) */ + int depth = 1; + while (i < limit) { + c = s.charAt(i); + i++; + if (c == '(') + depth++; + else if (c == ')') + if (--depth <= 0) + break; + } + continue; + } + if ('0' <= c && c <= '9') { + n = c - '0'; + while (i < limit && '0' <= (c = s.charAt(i)) && c <= '9') { + n = n * 10 + c - '0'; + i++; + } + + /* allow TZA before the year, so + * 'Wed Nov 05 21:49:11 GMT-0800 1997' + * works */ + + /* uses of seenplusminus allow : in TZA, so Java + * no-timezone style of GMT+4:30 works + */ + if ((prevc == '+' || prevc == '-')/* && year>=0 */) { + /* make ':' case below change tzoffset */ + seenplusminus = true; + + /* offset */ + if (n < 24) + n = n * 60; /* EG. "GMT-3" */ + else + n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */ + if (prevc == '+') /* plus means east of GMT */ + n = -n; + if (tzoffset != 0 && tzoffset != -1) + return ScriptRuntime.NaN; + tzoffset = n; + } else if (n >= 70 || + (prevc == '/' && mon >= 0 && mday >= 0 + && year < 0)) + { + if (year >= 0) + return ScriptRuntime.NaN; + else if (c <= ' ' || c == ',' || c == '/' || i >= limit) + year = n < 100 ? n + 1900 : n; + else + return ScriptRuntime.NaN; + } else if (c == ':') { + if (hour < 0) + hour = /*byte*/ n; + else if (min < 0) + min = /*byte*/ n; + else + return ScriptRuntime.NaN; + } else if (c == '/') { + if (mon < 0) + mon = /*byte*/ n-1; + else if (mday < 0) + mday = /*byte*/ n; + else + return ScriptRuntime.NaN; + } else if (i < limit && c != ',' && c > ' ' && c != '-') { + return ScriptRuntime.NaN; + } else if (seenplusminus && n < 60) { /* handle GMT-3:30 */ + if (tzoffset < 0) + tzoffset -= n; + else + tzoffset += n; + } else if (hour >= 0 && min < 0) { + min = /*byte*/ n; + } else if (min >= 0 && sec < 0) { + sec = /*byte*/ n; + } else if (mday < 0) { + mday = /*byte*/ n; + } else { + return ScriptRuntime.NaN; + } + prevc = 0; + } else if (c == '/' || c == ':' || c == '+' || c == '-') { + prevc = c; + } else { + int st = i - 1; + while (i < limit) { + c = s.charAt(i); + if (!(('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'))) + break; + i++; + } + int letterCount = i - st; + if (letterCount < 2) + return ScriptRuntime.NaN; + /* + * Use ported code from jsdate.c rather than the locale-specific + * date-parsing code from Java, to keep js and rhino consistent. + * Is this the right strategy? + */ + String wtb = "am;pm;" + +"monday;tuesday;wednesday;thursday;friday;" + +"saturday;sunday;" + +"january;february;march;april;may;june;" + +"july;august;september;october;november;december;" + +"gmt;ut;utc;est;edt;cst;cdt;mst;mdt;pst;pdt;"; + int index = 0; + for (int wtbOffset = 0; ;) { + int wtbNext = wtb.indexOf(';', wtbOffset); + if (wtbNext < 0) + return ScriptRuntime.NaN; + if (wtb.regionMatches(true, wtbOffset, s, st, letterCount)) + break; + wtbOffset = wtbNext + 1; + ++index; + } + if (index < 2) { + /* + * AM/PM. Count 12:30 AM as 00:30, 12:30 PM as + * 12:30, instead of blindly adding 12 if PM. + */ + if (hour > 12 || hour < 0) { + return ScriptRuntime.NaN; + } else if (index == 0) { + // AM + if (hour == 12) + hour = 0; + } else { + // PM + if (hour != 12) + hour += 12; + } + } else if ((index -= 2) < 7) { + // ignore week days + } else if ((index -= 7) < 12) { + // month + if (mon < 0) { + mon = index; + } else { + return ScriptRuntime.NaN; + } + } else { + index -= 12; + // timezones + switch (index) { + case 0 /* gmt */: tzoffset = 0; break; + case 1 /* ut */: tzoffset = 0; break; + case 2 /* utc */: tzoffset = 0; break; + case 3 /* est */: tzoffset = 5 * 60; break; + case 4 /* edt */: tzoffset = 4 * 60; break; + case 5 /* cst */: tzoffset = 6 * 60; break; + case 6 /* cdt */: tzoffset = 5 * 60; break; + case 7 /* mst */: tzoffset = 7 * 60; break; + case 8 /* mdt */: tzoffset = 6 * 60; break; + case 9 /* pst */: tzoffset = 8 * 60; break; + case 10 /* pdt */:tzoffset = 7 * 60; break; + default: Kit.codeBug(); + } + } + } + } + if (year < 0 || mon < 0 || mday < 0) + return ScriptRuntime.NaN; + if (sec < 0) + sec = 0; + if (min < 0) + min = 0; + if (hour < 0) + hour = 0; + + double msec = date_msecFromDate(year, mon, mday, hour, min, sec, 0); + if (tzoffset == -1) { /* no time zone specified, have to use local */ + return internalUTC(msec); + } else { + return msec + tzoffset * msPerMinute; + } + } + + private static String date_format(double t, int methodId) + { + StringBuffer result = new StringBuffer(60); + double local = LocalTime(t); + + /* Tue Oct 31 09:41:40 GMT-0800 (PST) 2000 */ + /* Tue Oct 31 2000 */ + /* 09:41:40 GMT-0800 (PST) */ + + if (methodId != Id_toTimeString) { + appendWeekDayName(result, WeekDay(local)); + result.append(' '); + appendMonthName(result, MonthFromTime(local)); + result.append(' '); + append0PaddedUint(result, DateFromTime(local), 2); + result.append(' '); + int year = YearFromTime(local); + if (year < 0) { + result.append('-'); + year = -year; + } + append0PaddedUint(result, year, 4); + if (methodId != Id_toDateString) + result.append(' '); + } + + if (methodId != Id_toDateString) { + append0PaddedUint(result, HourFromTime(local), 2); + result.append(':'); + append0PaddedUint(result, MinFromTime(local), 2); + result.append(':'); + append0PaddedUint(result, SecFromTime(local), 2); + + // offset from GMT in minutes. The offset includes daylight + // savings, if it applies. + int minutes = (int) Math.floor((LocalTZA + DaylightSavingTA(t)) + / msPerMinute); + // map 510 minutes to 0830 hours + int offset = (minutes / 60) * 100 + minutes % 60; + if (offset > 0) { + result.append(" GMT+"); + } else { + result.append(" GMT-"); + offset = -offset; + } + append0PaddedUint(result, offset, 4); + + if (timeZoneFormatter == null) + timeZoneFormatter = new java.text.SimpleDateFormat("zzz"); + + // Find an equivalent year before getting the timezone + // comment. See DaylightSavingTA. + if (t < 0.0 || t > 2145916800000.0) { + int equiv = EquivalentYear(YearFromTime(local)); + double day = MakeDay(equiv, MonthFromTime(t), DateFromTime(t)); + t = MakeDate(day, TimeWithinDay(t)); + } + result.append(" ("); + java.util.Date date = new Date((long) t); + synchronized (timeZoneFormatter) { + result.append(timeZoneFormatter.format(date)); + } + result.append(')'); + } + return result.toString(); + } + + /* the javascript constructor */ + private static Object jsConstructor(Object[] args) + { + NativeDate obj = new NativeDate(); + + // if called as a constructor with no args, + // return a new Date with the current time. + if (args.length == 0) { + obj.date = now(); + return obj; + } + + // if called with just one arg - + if (args.length == 1) { + Object arg0 = args[0]; + if (arg0 instanceof Scriptable) + arg0 = ((Scriptable) arg0).getDefaultValue(null); + double date; + if (arg0 instanceof String) { + // it's a string; parse it. + date = date_parseString((String)arg0); + } else { + // if it's not a string, use it as a millisecond date + date = ScriptRuntime.toNumber(arg0); + } + obj.date = TimeClip(date); + return obj; + } + + double time = date_msecFromArgs(args); + + if (!Double.isNaN(time) && !Double.isInfinite(time)) + time = TimeClip(internalUTC(time)); + + obj.date = time; + + return obj; + } + + private static String toLocale_helper(double t, int methodId) + { + java.text.DateFormat formatter; + switch (methodId) { + case Id_toLocaleString: + if (localeDateTimeFormatter == null) { + localeDateTimeFormatter + = DateFormat.getDateTimeInstance(DateFormat.LONG, + DateFormat.LONG); + } + formatter = localeDateTimeFormatter; + break; + case Id_toLocaleTimeString: + if (localeTimeFormatter == null) { + localeTimeFormatter + = DateFormat.getTimeInstance(DateFormat.LONG); + } + formatter = localeTimeFormatter; + break; + case Id_toLocaleDateString: + if (localeDateFormatter == null) { + localeDateFormatter + = DateFormat.getDateInstance(DateFormat.LONG); + } + formatter = localeDateFormatter; + break; + default: formatter = null; // unreachable + } + + synchronized (formatter) { + return formatter.format(new Date((long) t)); + } + } + + private static String js_toUTCString(double date) + { + StringBuffer result = new StringBuffer(60); + + appendWeekDayName(result, WeekDay(date)); + result.append(", "); + append0PaddedUint(result, DateFromTime(date), 2); + result.append(' '); + appendMonthName(result, MonthFromTime(date)); + result.append(' '); + int year = YearFromTime(date); + if (year < 0) { + result.append('-'); year = -year; + } + append0PaddedUint(result, year, 4); + result.append(' '); + append0PaddedUint(result, HourFromTime(date), 2); + result.append(':'); + append0PaddedUint(result, MinFromTime(date), 2); + result.append(':'); + append0PaddedUint(result, SecFromTime(date), 2); + result.append(" GMT"); + return result.toString(); + } + + private static void append0PaddedUint(StringBuffer sb, int i, int minWidth) + { + if (i < 0) Kit.codeBug(); + int scale = 1; + --minWidth; + if (i >= 10) { + if (i < 1000 * 1000 * 1000) { + for (;;) { + int newScale = scale * 10; + if (i < newScale) { break; } + --minWidth; + scale = newScale; + } + } else { + // Separated case not to check against 10 * 10^9 overflow + minWidth -= 9; + scale = 1000 * 1000 * 1000; + } + } + while (minWidth > 0) { + sb.append('0'); + --minWidth; + } + while (scale != 1) { + sb.append((char)('0' + (i / scale))); + i %= scale; + scale /= 10; + } + sb.append((char)('0' + i)); + } + + private static void appendMonthName(StringBuffer sb, int index) + { + // Take advantage of the fact that all month abbreviations + // have the same length to minimize amount of strings runtime has + // to keep in memory + String months = "Jan"+"Feb"+"Mar"+"Apr"+"May"+"Jun" + +"Jul"+"Aug"+"Sep"+"Oct"+"Nov"+"Dec"; + index *= 3; + for (int i = 0; i != 3; ++i) { + sb.append(months.charAt(index + i)); + } + } + + private static void appendWeekDayName(StringBuffer sb, int index) + { + String days = "Sun"+"Mon"+"Tue"+"Wed"+"Thu"+"Fri"+"Sat"; + index *= 3; + for (int i = 0; i != 3; ++i) { + sb.append(days.charAt(index + i)); + } + } + + private static double makeTime(double date, Object[] args, int methodId) + { + int maxargs; + boolean local = true; + switch (methodId) { + case Id_setUTCMilliseconds: + local = false; + // fallthrough + case Id_setMilliseconds: + maxargs = 1; + break; + + case Id_setUTCSeconds: + local = false; + // fallthrough + case Id_setSeconds: + maxargs = 2; + break; + + case Id_setUTCMinutes: + local = false; + // fallthrough + case Id_setMinutes: + maxargs = 3; + break; + + case Id_setUTCHours: + local = false; + // fallthrough + case Id_setHours: + maxargs = 4; + break; + + default: + Kit.codeBug(); + maxargs = 0; + } + + int i; + double conv[] = new double[4]; + double hour, min, sec, msec; + double lorutime; /* Local or UTC version of date */ + + double time; + double result; + + /* just return NaN if the date is already NaN */ + if (date != date) + return date; + + /* Satisfy the ECMA rule that if a function is called with + * fewer arguments than the specified formal arguments, the + * remaining arguments are set to undefined. Seems like all + * the Date.setWhatever functions in ECMA are only varargs + * beyond the first argument; this should be set to undefined + * if it's not given. This means that "d = new Date(); + * d.setMilliseconds()" returns NaN. Blech. + */ + if (args.length == 0) + args = ScriptRuntime.padArguments(args, 1); + + for (i = 0; i < args.length && i < maxargs; i++) { + conv[i] = ScriptRuntime.toNumber(args[i]); + + // limit checks that happen in MakeTime in ECMA. + if (conv[i] != conv[i] || Double.isInfinite(conv[i])) { + return ScriptRuntime.NaN; + } + conv[i] = ScriptRuntime.toInteger(conv[i]); + } + + if (local) + lorutime = LocalTime(date); + else + lorutime = date; + + i = 0; + int stop = args.length; + + if (maxargs >= 4 && i < stop) + hour = conv[i++]; + else + hour = HourFromTime(lorutime); + + if (maxargs >= 3 && i < stop) + min = conv[i++]; + else + min = MinFromTime(lorutime); + + if (maxargs >= 2 && i < stop) + sec = conv[i++]; + else + sec = SecFromTime(lorutime); + + if (maxargs >= 1 && i < stop) + msec = conv[i++]; + else + msec = msFromTime(lorutime); + + time = MakeTime(hour, min, sec, msec); + result = MakeDate(Day(lorutime), time); + + if (local) + result = internalUTC(result); + date = TimeClip(result); + + return date; + } + + private static double makeDate(double date, Object[] args, int methodId) + { + int maxargs; + boolean local = true; + switch (methodId) { + case Id_setUTCDate: + local = false; + // fallthrough + case Id_setDate: + maxargs = 1; + break; + + case Id_setUTCMonth: + local = false; + // fallthrough + case Id_setMonth: + maxargs = 2; + break; + + case Id_setUTCFullYear: + local = false; + // fallthrough + case Id_setFullYear: + maxargs = 3; + break; + + default: + Kit.codeBug(); + maxargs = 0; + } + + int i; + double conv[] = new double[3]; + double year, month, day; + double lorutime; /* local or UTC version of date */ + double result; + + /* See arg padding comment in makeTime.*/ + if (args.length == 0) + args = ScriptRuntime.padArguments(args, 1); + + for (i = 0; i < args.length && i < maxargs; i++) { + conv[i] = ScriptRuntime.toNumber(args[i]); + + // limit checks that happen in MakeDate in ECMA. + if (conv[i] != conv[i] || Double.isInfinite(conv[i])) { + return ScriptRuntime.NaN; + } + conv[i] = ScriptRuntime.toInteger(conv[i]); + } + + /* return NaN if date is NaN and we're not setting the year, + * If we are, use 0 as the time. */ + if (date != date) { + if (args.length < 3) { + return ScriptRuntime.NaN; + } else { + lorutime = 0; + } + } else { + if (local) + lorutime = LocalTime(date); + else + lorutime = date; + } + + i = 0; + int stop = args.length; + + if (maxargs >= 3 && i < stop) + year = conv[i++]; + else + year = YearFromTime(lorutime); + + if (maxargs >= 2 && i < stop) + month = conv[i++]; + else + month = MonthFromTime(lorutime); + + if (maxargs >= 1 && i < stop) + day = conv[i++]; + else + day = DateFromTime(lorutime); + + day = MakeDay(year, month, day); /* day within year */ + result = MakeDate(day, TimeWithinDay(lorutime)); + + if (local) + result = internalUTC(result); + + date = TimeClip(result); + + return date; + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:38 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 6: X="getDay";id=Id_getDay; break L; + case 7: switch (s.charAt(3)) { + case 'D': c=s.charAt(0); + if (c=='g') { X="getDate";id=Id_getDate; } + else if (c=='s') { X="setDate";id=Id_setDate; } + break L; + case 'T': c=s.charAt(0); + if (c=='g') { X="getTime";id=Id_getTime; } + else if (c=='s') { X="setTime";id=Id_setTime; } + break L; + case 'Y': c=s.charAt(0); + if (c=='g') { X="getYear";id=Id_getYear; } + else if (c=='s') { X="setYear";id=Id_setYear; } + break L; + case 'u': X="valueOf";id=Id_valueOf; break L; + } break L; + case 8: switch (s.charAt(3)) { + case 'H': c=s.charAt(0); + if (c=='g') { X="getHours";id=Id_getHours; } + else if (c=='s') { X="setHours";id=Id_setHours; } + break L; + case 'M': c=s.charAt(0); + if (c=='g') { X="getMonth";id=Id_getMonth; } + else if (c=='s') { X="setMonth";id=Id_setMonth; } + break L; + case 'o': X="toSource";id=Id_toSource; break L; + case 't': X="toString";id=Id_toString; break L; + } break L; + case 9: X="getUTCDay";id=Id_getUTCDay; break L; + case 10: c=s.charAt(3); + if (c=='M') { + c=s.charAt(0); + if (c=='g') { X="getMinutes";id=Id_getMinutes; } + else if (c=='s') { X="setMinutes";id=Id_setMinutes; } + } + else if (c=='S') { + c=s.charAt(0); + if (c=='g') { X="getSeconds";id=Id_getSeconds; } + else if (c=='s') { X="setSeconds";id=Id_setSeconds; } + } + else if (c=='U') { + c=s.charAt(0); + if (c=='g') { X="getUTCDate";id=Id_getUTCDate; } + else if (c=='s') { X="setUTCDate";id=Id_setUTCDate; } + } + break L; + case 11: switch (s.charAt(3)) { + case 'F': c=s.charAt(0); + if (c=='g') { X="getFullYear";id=Id_getFullYear; } + else if (c=='s') { X="setFullYear";id=Id_setFullYear; } + break L; + case 'M': X="toGMTString";id=Id_toGMTString; break L; + case 'T': X="toUTCString";id=Id_toUTCString; break L; + case 'U': c=s.charAt(0); + if (c=='g') { + c=s.charAt(9); + if (c=='r') { X="getUTCHours";id=Id_getUTCHours; } + else if (c=='t') { X="getUTCMonth";id=Id_getUTCMonth; } + } + else if (c=='s') { + c=s.charAt(9); + if (c=='r') { X="setUTCHours";id=Id_setUTCHours; } + else if (c=='t') { X="setUTCMonth";id=Id_setUTCMonth; } + } + break L; + case 's': X="constructor";id=Id_constructor; break L; + } break L; + case 12: c=s.charAt(2); + if (c=='D') { X="toDateString";id=Id_toDateString; } + else if (c=='T') { X="toTimeString";id=Id_toTimeString; } + break L; + case 13: c=s.charAt(0); + if (c=='g') { + c=s.charAt(6); + if (c=='M') { X="getUTCMinutes";id=Id_getUTCMinutes; } + else if (c=='S') { X="getUTCSeconds";id=Id_getUTCSeconds; } + } + else if (c=='s') { + c=s.charAt(6); + if (c=='M') { X="setUTCMinutes";id=Id_setUTCMinutes; } + else if (c=='S') { X="setUTCSeconds";id=Id_setUTCSeconds; } + } + break L; + case 14: c=s.charAt(0); + if (c=='g') { X="getUTCFullYear";id=Id_getUTCFullYear; } + else if (c=='s') { X="setUTCFullYear";id=Id_setUTCFullYear; } + else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; } + break L; + case 15: c=s.charAt(0); + if (c=='g') { X="getMilliseconds";id=Id_getMilliseconds; } + else if (c=='s') { X="setMilliseconds";id=Id_setMilliseconds; } + break L; + case 17: X="getTimezoneOffset";id=Id_getTimezoneOffset; break L; + case 18: c=s.charAt(0); + if (c=='g') { X="getUTCMilliseconds";id=Id_getUTCMilliseconds; } + else if (c=='s') { X="setUTCMilliseconds";id=Id_setUTCMilliseconds; } + else if (c=='t') { + c=s.charAt(8); + if (c=='D') { X="toLocaleDateString";id=Id_toLocaleDateString; } + else if (c=='T') { X="toLocaleTimeString";id=Id_toLocaleTimeString; } + } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + ConstructorId_now = -3, + ConstructorId_parse = -2, + ConstructorId_UTC = -1, + + Id_constructor = 1, + Id_toString = 2, + Id_toTimeString = 3, + Id_toDateString = 4, + Id_toLocaleString = 5, + Id_toLocaleTimeString = 6, + Id_toLocaleDateString = 7, + Id_toUTCString = 8, + Id_toSource = 9, + Id_valueOf = 10, + Id_getTime = 11, + Id_getYear = 12, + Id_getFullYear = 13, + Id_getUTCFullYear = 14, + Id_getMonth = 15, + Id_getUTCMonth = 16, + Id_getDate = 17, + Id_getUTCDate = 18, + Id_getDay = 19, + Id_getUTCDay = 20, + Id_getHours = 21, + Id_getUTCHours = 22, + Id_getMinutes = 23, + Id_getUTCMinutes = 24, + Id_getSeconds = 25, + Id_getUTCSeconds = 26, + Id_getMilliseconds = 27, + Id_getUTCMilliseconds = 28, + Id_getTimezoneOffset = 29, + Id_setTime = 30, + Id_setMilliseconds = 31, + Id_setUTCMilliseconds = 32, + Id_setSeconds = 33, + Id_setUTCSeconds = 34, + Id_setMinutes = 35, + Id_setUTCMinutes = 36, + Id_setHours = 37, + Id_setUTCHours = 38, + Id_setDate = 39, + Id_setUTCDate = 40, + Id_setMonth = 41, + Id_setUTCMonth = 42, + Id_setFullYear = 43, + Id_setUTCFullYear = 44, + Id_setYear = 45, + + MAX_PROTOTYPE_ID = 45; + + private static final int + Id_toGMTString = Id_toUTCString; // Alias, see Ecma B.2.6 +// #/string_id_map# + + /* cached values */ + private static java.util.TimeZone thisTimeZone; + private static double LocalTZA; + private static java.text.DateFormat timeZoneFormatter; + private static java.text.DateFormat localeDateTimeFormatter; + private static java.text.DateFormat localeDateFormatter; + private static java.text.DateFormat localeTimeFormatter; + + private double date; +} + + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeError.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeError.java new file mode 100644 index 0000000..4aff10c --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeError.java @@ -0,0 +1,227 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Roger Lawrence + * + * 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; + +/** + * + * The class of error objects + * + * ECMA 15.11 + */ +final class NativeError extends IdScriptableObject +{ + static final long serialVersionUID = -5338413581437645187L; + + private static final Object ERROR_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeError obj = new NativeError(); + ScriptableObject.putProperty(obj, "name", "Error"); + ScriptableObject.putProperty(obj, "message", ""); + ScriptableObject.putProperty(obj, "fileName", ""); + ScriptableObject.putProperty(obj, "lineNumber", new Integer(0)); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + static NativeError make(Context cx, Scriptable scope, + IdFunctionObject ctorObj, Object[] args) + { + Scriptable proto = (Scriptable)(ctorObj.get("prototype", ctorObj)); + + NativeError obj = new NativeError(); + obj.setPrototype(proto); + obj.setParentScope(scope); + + int arglen = args.length; + if (arglen >= 1) { + ScriptableObject.putProperty(obj, "message", + ScriptRuntime.toString(args[0])); + if (arglen >= 2) { + ScriptableObject.putProperty(obj, "fileName", args[1]); + if (arglen >= 3) { + int line = ScriptRuntime.toInt32(args[2]); + ScriptableObject.putProperty(obj, "lineNumber", + new Integer(line)); + } + } + } + if(arglen < 3 && cx.hasFeature(Context.FEATURE_LOCATION_INFORMATION_IN_ERROR)) { + // Fill in fileName and lineNumber automatically when not specified + // explicitly, see Bugzilla issue #342807 + int[] linep = new int[1]; + String fileName = Context.getSourcePositionFromStack(linep); + ScriptableObject.putProperty(obj, "lineNumber", + new Integer(linep[0])); + if(arglen < 2) { + ScriptableObject.putProperty(obj, "fileName", fileName); + } + } + return obj; + } + + public String getClassName() + { + return "Error"; + } + + public String toString() + { + return js_toString(this); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toSource: arity=0; s="toSource"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(ERROR_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(ERROR_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: + return make(cx, scope, f, args); + + case Id_toString: + return js_toString(thisObj); + + case Id_toSource: + return js_toSource(cx, scope, thisObj); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private static String js_toString(Scriptable thisObj) + { + return getString(thisObj, "name")+": "+getString(thisObj, "message"); + } + + private static String js_toSource(Context cx, Scriptable scope, + Scriptable thisObj) + { + // Emulation of SpiderMonkey behavior + Object name = ScriptableObject.getProperty(thisObj, "name"); + Object message = ScriptableObject.getProperty(thisObj, "message"); + Object fileName = ScriptableObject.getProperty(thisObj, "fileName"); + Object lineNumber = ScriptableObject.getProperty(thisObj, "lineNumber"); + + StringBuffer sb = new StringBuffer(); + sb.append("(new "); + if (name == NOT_FOUND) { + name = Undefined.instance; + } + sb.append(ScriptRuntime.toString(name)); + sb.append("("); + if (message != NOT_FOUND + || fileName != NOT_FOUND + || lineNumber != NOT_FOUND) + { + if (message == NOT_FOUND) { + message = ""; + } + sb.append(ScriptRuntime.uneval(cx, scope, message)); + if (fileName != NOT_FOUND || lineNumber != NOT_FOUND) { + sb.append(", "); + if (fileName == NOT_FOUND) { + fileName = ""; + } + sb.append(ScriptRuntime.uneval(cx, scope, fileName)); + if (lineNumber != NOT_FOUND) { + int line = ScriptRuntime.toInt32(lineNumber); + if (line != 0) { + sb.append(", "); + sb.append(ScriptRuntime.toString(line)); + } + } + } + } + sb.append("))"); + return sb.toString(); + } + + private static String getString(Scriptable obj, String id) + { + Object value = ScriptableObject.getProperty(obj, id); + if (value == NOT_FOUND) return ""; + return ScriptRuntime.toString(value); + } + + protected int findPrototypeId(String s) + { + int id; +// #string_id_map# +// #generated# Last update: 2007-05-09 08:15:45 EDT + L0: { id = 0; String X = null; int c; + int s_length = s.length(); + if (s_length==8) { + c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + } + else if (s_length==11) { X="constructor";id=Id_constructor; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toSource = 3, + + MAX_PROTOTYPE_ID = 3; + +// #/string_id_map# +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeFunction.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeFunction.java new file mode 100644 index 0000000..ac70556 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeFunction.java @@ -0,0 +1,169 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Mike McCabe + * + * 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 org.mozilla.javascript.debug.DebuggableScript; + +/** + * This class implements the Function native object. + * See ECMA 15.3. + * @author Norris Boyd + */ +public abstract class NativeFunction extends BaseFunction +{ + + public final void initScriptFunction(Context cx, Scriptable scope) + { + ScriptRuntime.setFunctionProtoAndParent(this, scope); + } + + /** + * @param indent How much to indent the decompiled result + * + * @param flags Flags specifying format of decompilation output + */ + final String decompile(int indent, int flags) + { + String encodedSource = getEncodedSource(); + if (encodedSource == null) { + return super.decompile(indent, flags); + } else { + UintMap properties = new UintMap(1); + properties.put(Decompiler.INITIAL_INDENT_PROP, indent); + return Decompiler.decompile(encodedSource, flags, properties); + } + } + + public int getLength() + { + int paramCount = getParamCount(); + if (getLanguageVersion() != Context.VERSION_1_2) { + return paramCount; + } + Context cx = Context.getContext(); + NativeCall activation = ScriptRuntime.findFunctionActivation(cx, this); + if (activation == null) { + return paramCount; + } + return activation.originalArgs.length; + } + + public int getArity() + { + return getParamCount(); + } + + /** + * @deprecated Use {@link BaseFunction#getFunctionName()} instead. + * For backwards compatibility keep an old method name used by + * Batik and possibly others. + */ + public String jsGet_name() + { + return getFunctionName(); + } + + /** + * Get encoded source string. + */ + public String getEncodedSource() + { + return null; + } + + public DebuggableScript getDebuggableView() + { + return null; + } + + /** + * Resume execution of a suspended generator. + * @param cx The current context + * @param scope Scope for the parent generator function + * @param operation The resumption operation (next, send, etc.. ) + * @param state The generator state (has locals, stack, etc.) + * @param value The return value of yield (if required). + * @return The next yielded value (if any) + */ + public Object resumeGenerator(Context cx, Scriptable scope, + int operation, Object state, Object value) + { + throw new EvaluatorException("resumeGenerator() not implemented"); + } + + + protected abstract int getLanguageVersion(); + + /** + * Get number of declared parameters. It should be 0 for scripts. + */ + protected abstract int getParamCount(); + + /** + * Get number of declared parameters and variables defined through var + * statements. + */ + protected abstract int getParamAndVarCount(); + + /** + * Get parameter or variable name. + * If <tt>index < {@link #getParamCount()}</tt>, then return the name of the + * corresponding parameter. Otherwise return the name of variable. + */ + protected abstract String getParamOrVarName(int index); + + /** + * Get parameter or variable const-ness. + * If <tt>index < {@link #getParamCount()}</tt>, then return the const-ness + * of the corresponding parameter. Otherwise return whether the variable is + * const. + */ + protected boolean getParamOrVarConst(int index) + { + // By default return false to preserve compatibility with existing + // classes subclassing this class, which are mostly generated by jsc + // from earlier Rhino versions. See Bugzilla #396117. + return false; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGenerator.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGenerator.java new file mode 100644 index 0000000..0a8da9f --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGenerator.java @@ -0,0 +1,281 @@ +/* -*- 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. + * + * Contributor(s): + * Norris Boyd + * + * 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; + +/** + * This class implements generator objects. See + * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Generators + * + * @author Norris Boyd + */ +public final class NativeGenerator extends IdScriptableObject { + private static final Object GENERATOR_TAG = new Object(); + + static NativeGenerator init(ScriptableObject scope, boolean sealed) { + // Generator + // Can't use "NativeGenerator().exportAsJSClass" since we don't want + // to define "Generator" as a constructor in the top-level scope. + + NativeGenerator prototype = new NativeGenerator(); + if (scope != null) { + prototype.setParentScope(scope); + prototype.setPrototype(getObjectPrototype(scope)); + } + prototype.activatePrototypeMap(MAX_PROTOTYPE_ID); + if (sealed) { + prototype.sealObject(); + } + + // Need to access Generator prototype when constructing + // Generator instances, but don't have a generator constructor + // to use to find the prototype. Use the "associateValue" + // approach instead. + if (scope != null) { + scope.associateValue(GENERATOR_TAG, prototype); + } + + return prototype; + } + + /** + * Only for constructing the prototype object. + */ + private NativeGenerator() { } + + public NativeGenerator(Scriptable scope, NativeFunction function, + Object savedState) + { + this.function = function; + this.savedState = savedState; + // Set parent and prototype properties. Since we don't have a + // "Generator" constructor in the top scope, we stash the + // prototype in the top scope's associated value. + Scriptable top = ScriptableObject.getTopLevelScope(scope); + this.setParentScope(top); + NativeGenerator prototype = (NativeGenerator) + ScriptableObject.getTopScopeValue(top, GENERATOR_TAG); + this.setPrototype(prototype); + } + + public static final int GENERATOR_SEND = 0, + GENERATOR_THROW = 1, + GENERATOR_CLOSE = 2; + + public String getClassName() { + return "Generator"; + } + + /** + * Close the generator if it is still open. + */ + public void finalize() throws Throwable { + if (savedState != null) { + // This is a little tricky since we are most likely running in + // a different thread. We need to get a Context to run this, and + // we must call "doTopCall" since this will likely be the outermost + // JavaScript frame on this thread. + Context cx = Context.getCurrentContext(); + ContextFactory factory = cx != null ? cx.getFactory() + : ContextFactory.getGlobal(); + Scriptable scope = ScriptableObject.getTopLevelScope(this); + factory.call(new CloseGeneratorAction(this)); + } + } + + private static class CloseGeneratorAction implements ContextAction { + private NativeGenerator generator; + + CloseGeneratorAction(NativeGenerator generator) { + this.generator = generator; + } + + public Object run(Context cx) { + Scriptable scope = ScriptableObject.getTopLevelScope(generator); + Callable closeGenerator = new Callable() { + public Object call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) { + return ((NativeGenerator)thisObj).resume(cx, scope, + GENERATOR_CLOSE, new GeneratorClosedException()); + } + }; + return ScriptRuntime.doTopCall(closeGenerator, cx, scope, + generator, null); + } + } + + protected void initPrototypeId(int id) { + String s; + int arity; + switch (id) { + case Id_close: arity=1; s="close"; break; + case Id_next: arity=1; s="next"; break; + case Id_send: arity=0; s="send"; break; + case Id_throw: arity=0; s="throw"; break; + case Id___iterator__: arity=1; s="__iterator__"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(GENERATOR_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(GENERATOR_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + + if (!(thisObj instanceof NativeGenerator)) + throw incompatibleCallError(f); + + NativeGenerator generator = (NativeGenerator) thisObj; + + switch (id) { + + case Id_close: + // need to run any pending finally clauses + return generator.resume(cx, scope, GENERATOR_CLOSE, + new GeneratorClosedException()); + + case Id_next: + // arguments to next() are ignored + generator.firstTime = false; + return generator.resume(cx, scope, GENERATOR_SEND, + Undefined.instance); + + case Id_send: { + Object arg = args.length > 0 ? args[0] : Undefined.instance; + if (generator.firstTime && !arg.equals(Undefined.instance)) { + throw ScriptRuntime.typeError0("msg.send.newborn"); + } + return generator.resume(cx, scope, GENERATOR_SEND, arg); + } + + case Id_throw: + return generator.resume(cx, scope, GENERATOR_THROW, + args.length > 0 ? args[0] : Undefined.instance); + + case Id___iterator__: + return thisObj; + + default: + throw new IllegalArgumentException(String.valueOf(id)); + } + } + + private Object resume(Context cx, Scriptable scope, int operation, + Object value) + { + if (savedState == null) { + if (operation == GENERATOR_CLOSE) + return Undefined.instance; + Object thrown; + if (operation == GENERATOR_THROW) { + thrown = value; + } else { + thrown = NativeIterator.getStopIterationObject(scope); + } + throw new JavaScriptException(thrown, lineSource, lineNumber); + } + try { + synchronized (this) { + // generator execution is necessarily single-threaded and + // non-reentrant. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=349263 + if (locked) + throw ScriptRuntime.typeError0("msg.already.exec.gen"); + locked = true; + } + return function.resumeGenerator(cx, scope, operation, savedState, + value); + } catch (GeneratorClosedException e) { + // On closing a generator in the compile path, the generator + // throws a special exception. This ensures execution of all pending + // finalizers and will not get caught by user code. + return Undefined.instance; + } catch (RhinoException e) { + lineNumber = e.lineNumber(); + lineSource = e.lineSource(); + savedState = null; + throw e; + } finally { + synchronized (this) { + locked = false; + } + if (operation == GENERATOR_CLOSE) + savedState = null; + } + } + +// #string_id_map# + + protected int findPrototypeId(String s) { + int id; +// #generated# Last update: 2007-06-14 13:13:03 EDT + L0: { id = 0; String X = null; int c; + int s_length = s.length(); + if (s_length==4) { + c=s.charAt(0); + if (c=='n') { X="next";id=Id_next; } + else if (c=='s') { X="send";id=Id_send; } + } + else if (s_length==5) { + c=s.charAt(0); + if (c=='c') { X="close";id=Id_close; } + else if (c=='t') { X="throw";id=Id_throw; } + } + else if (s_length==12) { X="__iterator__";id=Id___iterator__; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_close = 1, + Id_next = 2, + Id_send = 3, + Id_throw = 4, + Id___iterator__ = 5, + MAX_PROTOTYPE_ID = 5; + +// #/string_id_map# + private NativeFunction function; + private Object savedState; + private String lineSource; + private int lineNumber; + private boolean firstTime = true; + private boolean locked; + + public static class GeneratorClosedException extends RuntimeException { + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGlobal.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGlobal.java new file mode 100644 index 0000000..58faad4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeGlobal.java @@ -0,0 +1,790 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * 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.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, + ScriptableObject.DONTENUM); + ScriptableObject.defineProperty( + scope, "Infinity", + ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), + ScriptableObject.DONTENUM); + ScriptableObject.defineProperty( + scope, "undefined", Undefined.instance, + ScriptableObject.DONTENUM); + + String[] errorMethods = Kit.semicolonSplit("" + +"ConversionError;" + +"EvalError;" + +"RangeError;" + +"ReferenceError;" + +"SyntaxError;" + +"TypeError;" + +"URIError;" + +"InternalError;" + +"JavaException;" + ); + + /* + Each error constructor gets its own Error object as a prototype, + with the 'name' property set to the name of the error. + */ + for (int i = 0; i < errorMethods.length; i++) { + String name = errorMethods[i]; + Scriptable errorProto = ScriptRuntime. + newObject(cx, scope, "Error", + ScriptRuntime.emptyArgs); + errorProto.put("name", errorProto, name); + if (sealed) { + if (errorProto instanceof ScriptableObject) { + ((ScriptableObject)errorProto).sealObject(); + } + } + IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, + Id_new_CommonError, + name, 1, scope); + ctor.markAsConstructor(errorProto); + if (sealed) { + ctor.sealObject(); + } + ctor.exportAsScopeProperty(); + } + } + + 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: { + boolean result; + if (args.length < 1) { + result = false; + } else { + double d = ScriptRuntime.toNumber(args[0]); + result = (d == d + && d != Double.POSITIVE_INFINITY + && d != Double.NEGATIVE_INFINITY); + } + return ScriptRuntime.wrapBoolean(result); + } + + 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 = (d != 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. + */ + private 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 (!Character.isWhitespace(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') { + radix = 8; + start++; + } + } + } + + double d = ScriptRuntime.stringToNumber(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] + */ + private 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 (!TokenStream.isJSSpace(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; + 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; + exponent = i; + continue; + + case '+': + case '-': + // Only allow '+' or '-' after 'e' or 'E' + if (exponent != i-1) + break; + continue; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + continue; + + default: + break; + } + break; + } + 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 imbedding. Blame + * for the strange constant names should be directed there. + */ + + private 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 (d != d || ((mask = (int) d) != d) || + 0 != (mask & ~(URL_XALPHAS | URL_XPALPHAS | URL_PATH))) + { + throw Context.reportRuntimeError0("msg.bad.esc.mask"); + } + } + + StringBuffer 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 StringBuffer(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 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; + } + + private Object js_eval(Context cx, Scriptable scope, Object[] args) + { + String m = ScriptRuntime.getMessage1("msg.cant.call.indirect", "eval"); + throw NativeGlobal.constructError(cx, "EvalError", m, scope); + } + + 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. + */ + 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. + */ + 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; + StringBuffer 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 StringBuffer(length + 3); + sb.append(str); + sb.setLength(k); + utf8buf = new byte[6]; + } + if (0xDC00 <= C && C <= 0xDFFF) { + throw Context.reportRuntimeError0("msg.bad.uri"); + } + int V; + if (C < 0xD800 || 0xDBFF < C) { + V = C; + } else { + k++; + if (k == length) { + throw Context.reportRuntimeError0("msg.bad.uri"); + } + char C2 = str.charAt(k); + if (!(0xDC00 <= C2 && C2 <= 0xDFFF)) { + throw Context.reportRuntimeError0("msg.bad.uri"); + } + 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 Context.reportRuntimeError0("msg.bad.uri"); + int B = unHex(str.charAt(k + 1), str.charAt(k + 2)); + if (B < 0) throw Context.reportRuntimeError0("msg.bad.uri"); + 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 Context.reportRuntimeError0("msg.bad.uri"); + } 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 Context.reportRuntimeError0("msg.bad.uri"); + } + if (k + 3 * utf8Tail > length) + throw Context.reportRuntimeError0("msg.bad.uri"); + for (int j = 0; j != utf8Tail; j++) { + if (str.charAt(k) != '%') + throw Context.reportRuntimeError0("msg.bad.uri"); + B = unHex(str.charAt(k + 1), str.charAt(k + 2)); + if (B < 0 || (B & 0xC0) != 0x80) + throw Context.reportRuntimeError0("msg.bad.uri"); + ucs4Char = (ucs4Char << 6) | (B & 0x3F); + k += 3; + } + // Check for overlongs and other should-not-present codes + if (ucs4Char < minUcs4Char + || ucs4Char == 0xFFFE || ucs4Char == 0xFFFF) + { + ucs4Char = 0xFFFD; + } + if (ucs4Char >= 0x10000) { + ucs4Char -= 0x10000; + if (ucs4Char > 0xFFFFF) + throw Context.reportRuntimeError0("msg.bad.uri"); + 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 final String URI_DECODE_RESERVED = ";/?:@&=+$,#"; + + /* 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 = new Object(); + + 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; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeIterator.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeIterator.java new file mode 100644 index 0000000..c61f417 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeIterator.java @@ -0,0 +1,260 @@ +/* -*- 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. + * + * Contributor(s): + * Norris Boyd + * + * 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.util.Iterator; + +/** + * This class implements iterator objects. See + * http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7#Iterators + * + * @author Norris Boyd + */ +public final class NativeIterator extends IdScriptableObject { + private static final Object ITERATOR_TAG = new Object(); + + static void init(ScriptableObject scope, boolean sealed) { + // Iterator + NativeIterator iterator = new NativeIterator(); + iterator.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + + // Generator + NativeGenerator.init(scope, sealed); + + // StopIteration + NativeObject obj = new StopIteration(); + obj.setPrototype(getObjectPrototype(scope)); + obj.setParentScope(scope); + if (sealed) { obj.sealObject(); } + ScriptableObject.defineProperty(scope, STOP_ITERATION, obj, + ScriptableObject.DONTENUM); + // Use "associateValue" so that generators can continue to + // throw StopIteration even if the property of the global + // scope is replaced or deleted. + scope.associateValue(ITERATOR_TAG, obj); + } + + /** + * Only for constructing the prototype object. + */ + private NativeIterator() { + } + + private NativeIterator(Object objectIterator) { + this.objectIterator = objectIterator; + } + + /** + * Get the value of the "StopIteration" object. Note that this value + * is stored in the top-level scope using "associateValue" so the + * value can still be found even if a script overwrites or deletes + * the global "StopIteration" property. + * @param scope a scope whose parent chain reaches a top-level scope + * @return the StopIteration object + */ + public static Object getStopIterationObject(Scriptable scope) { + Scriptable top = ScriptableObject.getTopLevelScope(scope); + return ScriptableObject.getTopScopeValue(top, ITERATOR_TAG); + } + + private static final String STOP_ITERATION = "StopIteration"; + public static final String ITERATOR_PROPERTY_NAME = "__iterator__"; + + static class StopIteration extends NativeObject { + public String getClassName() { + return STOP_ITERATION; + } + + /* StopIteration has custom instanceof behavior since it + * doesn't have a constructor. + */ + public boolean hasInstance(Scriptable instance) { + return instance instanceof StopIteration; + } + } + + public String getClassName() { + return "Iterator"; + } + + protected void initPrototypeId(int id) { + String s; + int arity; + switch (id) { + case Id_constructor: arity=2; s="constructor"; break; + case Id_next: arity=0; s="next"; break; + case Id___iterator__: arity=1; s=ITERATOR_PROPERTY_NAME; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(ITERATOR_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(ITERATOR_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + + if (id == Id_constructor) { + return jsConstructor(cx, scope, thisObj, args); + } + + if (!(thisObj instanceof NativeIterator)) + throw incompatibleCallError(f); + + NativeIterator iterator = (NativeIterator) thisObj; + + switch (id) { + + case Id_next: + return iterator.next(cx, scope); + + case Id___iterator__: + /// XXX: what about argument? SpiderMonkey apparently ignores it + return thisObj; + + default: + throw new IllegalArgumentException(String.valueOf(id)); + } + } + + /* the javascript constructor */ + private static Object jsConstructor(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (args.length == 0 || args[0] == null || + args[0] == Undefined.instance) + { + throw ScriptRuntime.typeError1("msg.no.properties", + ScriptRuntime.toString(args[0])); + } + Scriptable obj = ScriptRuntime.toObject(scope, args[0]); + boolean keyOnly = args.length > 1 && ScriptRuntime.toBoolean(args[1]); + if (thisObj != null) { + // Called as a function. Convert to iterator if possible. + + // For objects that implement java.lang.Iterable or + // java.util.Iterator, have JavaScript Iterator call the underlying + // iteration methods + Iterator iterator = + VMBridge.instance.getJavaIterator(cx, scope, obj); + if (iterator != null) { + scope = ScriptableObject.getTopLevelScope(scope); + return cx.getWrapFactory().wrap(cx, scope, + new WrappedJavaIterator(iterator, scope), + WrappedJavaIterator.class); + } + + // Otherwise, just call the runtime routine + Scriptable jsIterator = ScriptRuntime.toIterator(cx, scope, obj, + keyOnly); + if (jsIterator != null) { + return jsIterator; + } + } + + // Otherwise, just set up to iterate over the properties of the object. + // Do not call __iterator__ method. + Object objectIterator = ScriptRuntime.enumInit(obj, cx, + keyOnly ? ScriptRuntime.ENUMERATE_KEYS_NO_ITERATOR + : ScriptRuntime.ENUMERATE_ARRAY_NO_ITERATOR); + ScriptRuntime.setEnumNumbers(objectIterator, true); + NativeIterator result = new NativeIterator(objectIterator); + result.setPrototype(NativeIterator.getClassPrototype(scope, + result.getClassName())); + result.setParentScope(scope); + return result; + } + + private Object next(Context cx, Scriptable scope) { + Boolean b = ScriptRuntime.enumNext(this.objectIterator); + if (!b.booleanValue()) { + // Out of values. Throw StopIteration. + throw new JavaScriptException( + NativeIterator.getStopIterationObject(scope), null, 0); + } + return ScriptRuntime.enumId(this.objectIterator, cx); + } + + static public class WrappedJavaIterator + { + WrappedJavaIterator(Iterator iterator, Scriptable scope) { + this.iterator = iterator; + this.scope = scope; + } + + public Object next() { + if (!iterator.hasNext()) { + // Out of values. Throw StopIteration. + throw new JavaScriptException( + NativeIterator.getStopIterationObject(scope), null, 0); + } + return iterator.next(); + } + + public Object __iterator__(boolean b) { + return this; + } + + private Iterator iterator; + private Scriptable scope; + } + +// #string_id_map# + + protected int findPrototypeId(String s) { + int id; +// #generated# Last update: 2007-06-11 09:43:19 EDT + L0: { id = 0; String X = null; + int s_length = s.length(); + if (s_length==4) { X="next";id=Id_next; } + else if (s_length==11) { X="constructor";id=Id_constructor; } + else if (s_length==12) { X="__iterator__";id=Id___iterator__; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_next = 2, + Id___iterator__ = 3, + MAX_PROTOTYPE_ID = 3; + +// #/string_id_map# + + private Object objectIterator; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaArray.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaArray.java new file mode 100644 index 0000000..2f711a0 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaArray.java @@ -0,0 +1,168 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Frank Mitchell + * Mike Shaver + * Kemal Bayram + * + * 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.Array; + +/** + * This class reflects Java arrays into the JavaScript environment. + * + * @author Mike Shaver + * @see NativeJavaClass + * @see NativeJavaObject + * @see NativeJavaPackage + */ + +public class NativeJavaArray extends NativeJavaObject +{ + static final long serialVersionUID = -924022554283675333L; + + public String getClassName() { + return "JavaArray"; + } + + public static NativeJavaArray wrap(Scriptable scope, Object array) { + return new NativeJavaArray(scope, array); + } + + public Object unwrap() { + return array; + } + + public NativeJavaArray(Scriptable scope, Object array) { + super(scope, null, ScriptRuntime.ObjectClass); + Class cl = array.getClass(); + if (!cl.isArray()) { + throw new RuntimeException("Array expected"); + } + this.array = array; + this.length = Array.getLength(array); + this.cls = cl.getComponentType(); + } + + public boolean has(String id, Scriptable start) { + return id.equals("length") || super.has(id, start); + } + + public boolean has(int index, Scriptable start) { + return 0 <= index && index < length; + } + + public Object get(String id, Scriptable start) { + if (id.equals("length")) + return new Integer(length); + Object result = super.get(id, start); + if (result == NOT_FOUND && + !ScriptableObject.hasProperty(getPrototype(), id)) + { + throw Context.reportRuntimeError2( + "msg.java.member.not.found", array.getClass().getName(), id); + } + return result; + } + + public Object get(int index, Scriptable start) { + if (0 <= index && index < length) { + Context cx = Context.getContext(); + Object obj = Array.get(array, index); + return cx.getWrapFactory().wrap(cx, this, obj, cls); + } + return Undefined.instance; + } + + public void put(String id, Scriptable start, Object value) { + // Ignore assignments to "length"--it's readonly. + if (!id.equals("length")) + throw Context.reportRuntimeError1( + "msg.java.array.member.not.found", id); + } + + public void put(int index, Scriptable start, Object value) { + if (0 <= index && index < length) { + Array.set(array, index, Context.jsToJava(value, cls)); + } + else { + throw Context.reportRuntimeError2( + "msg.java.array.index.out.of.bounds", String.valueOf(index), + String.valueOf(length - 1)); + } + } + + public Object getDefaultValue(Class hint) { + if (hint == null || hint == ScriptRuntime.StringClass) + return array.toString(); + if (hint == ScriptRuntime.BooleanClass) + return Boolean.TRUE; + if (hint == ScriptRuntime.NumberClass) + return ScriptRuntime.NaNobj; + return this; + } + + public Object[] getIds() { + Object[] result = new Object[length]; + int i = length; + while (--i >= 0) + result[i] = new Integer(i); + return result; + } + + public boolean hasInstance(Scriptable value) { + if (!(value instanceof Wrapper)) + return false; + Object instance = ((Wrapper)value).unwrap(); + return cls.isInstance(instance); + } + + public Scriptable getPrototype() { + if (prototype == null) { + prototype = + ScriptableObject.getClassPrototype(this.getParentScope(), + "Array"); + } + return prototype; + } + + Object array; + int length; + Class cls; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaClass.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaClass.java new file mode 100644 index 0000000..ab8af5c --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaClass.java @@ -0,0 +1,320 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * Kurt Westerfeld + * Kemal Bayram + * Ulrike Mueller <umueller@demandware.com> + * + * 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.util.Hashtable; + +/** + * This class reflects Java classes into the JavaScript environment, mainly + * for constructors and static members. We lazily reflect properties, + * and currently do not guarantee that a single j.l.Class is only + * reflected once into the JS environment, although we should. + * The only known case where multiple reflections + * are possible occurs when a j.l.Class is wrapped as part of a + * method return or property access, rather than by walking the + * Packages/java tree. + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaObject + * @see NativeJavaPackage + */ + +public class NativeJavaClass extends NativeJavaObject implements Function +{ + static final long serialVersionUID = -6460763940409461664L; + + // Special property for getting the underlying Java class object. + static final String javaClassPropertyName = "__javaObject__"; + + public NativeJavaClass() { + } + + public NativeJavaClass(Scriptable scope, Class cl) { + this.parent = scope; + this.javaObject = cl; + initMembers(); + } + + protected void initMembers() { + Class cl = (Class)javaObject; + members = JavaMembers.lookupClass(parent, cl, cl, false); + staticFieldAndMethods + = members.getFieldAndMethodsObjects(this, cl, true); + } + + public String getClassName() { + return "JavaClass"; + } + + public boolean has(String name, Scriptable start) { + return members.has(name, true) || javaClassPropertyName.equals(name); + } + + public Object get(String name, Scriptable start) { + // When used as a constructor, ScriptRuntime.newObject() asks + // for our prototype to create an object of the correct type. + // We don't really care what the object is, since we're returning + // one constructed out of whole cloth, so we return null. + if (name.equals("prototype")) + return null; + + if (staticFieldAndMethods != null) { + Object result = staticFieldAndMethods.get(name); + if (result != null) + return result; + } + + if (members.has(name, true)) { + return members.get(this, name, javaObject, true); + } + + if (javaClassPropertyName.equals(name)) { + Context cx = Context.getContext(); + Scriptable scope = ScriptableObject.getTopLevelScope(start); + return cx.getWrapFactory().wrap(cx, scope, javaObject, + ScriptRuntime.ClassClass); + } + + // experimental: look for nested classes by appending $name to + // current class' name. + Class nestedClass = findNestedClass(getClassObject(), name); + if (nestedClass != null) { + NativeJavaClass nestedValue = new NativeJavaClass + (ScriptableObject.getTopLevelScope(this), nestedClass); + nestedValue.setParentScope(this); + return nestedValue; + } + + throw members.reportMemberNotFound(name); + } + + public void put(String name, Scriptable start, Object value) { + members.put(this, name, javaObject, value, true); + } + + public Object[] getIds() { + return members.getIds(true); + } + + public Class getClassObject() { + return (Class) super.unwrap(); + } + + public Object getDefaultValue(Class hint) { + if (hint == null || hint == ScriptRuntime.StringClass) + return this.toString(); + if (hint == ScriptRuntime.BooleanClass) + return Boolean.TRUE; + if (hint == ScriptRuntime.NumberClass) + return ScriptRuntime.NaNobj; + return this; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + // If it looks like a "cast" of an object to this class type, + // walk the prototype chain to see if there's a wrapper of a + // object that's an instanceof this class. + if (args.length == 1 && args[0] instanceof Scriptable) { + Class c = getClassObject(); + Scriptable p = (Scriptable) args[0]; + do { + if (p instanceof Wrapper) { + Object o = ((Wrapper) p).unwrap(); + if (c.isInstance(o)) + return p; + } + p = p.getPrototype(); + } while (p != null); + } + return construct(cx, scope, args); + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + Class classObject = getClassObject(); + int modifiers = classObject.getModifiers(); + if (! (Modifier.isInterface(modifiers) || + Modifier.isAbstract(modifiers))) + { + MemberBox[] ctors = members.ctors; + int index = NativeJavaMethod.findFunction(cx, ctors, args); + if (index < 0) { + String sig = NativeJavaMethod.scriptSignature(args); + throw Context.reportRuntimeError2( + "msg.no.java.ctor", classObject.getName(), sig); + } + + // Found the constructor, so try invoking it. + return constructSpecific(cx, scope, args, ctors[index]); + } else { + Scriptable topLevel = ScriptableObject.getTopLevelScope(this); + String msg = ""; + try { + // trying to construct an interface; use JavaAdapter to + // construct a new class on the fly that implements this + // interface. + Object v = topLevel.get("JavaAdapter", topLevel); + if (v != NOT_FOUND) { + Function f = (Function) v; + Object[] adapterArgs = { this, args[0] }; + return f.construct(cx, topLevel,adapterArgs); + } + } catch (Exception ex) { + // fall through to error + String m = ex.getMessage(); + if (m != null) + msg = m; + } + throw Context.reportRuntimeError2( + "msg.cant.instantiate", msg, classObject.getName()); + } + } + + static Scriptable constructSpecific(Context cx, Scriptable scope, + Object[] args, MemberBox ctor) + { + Scriptable topLevel = ScriptableObject.getTopLevelScope(scope); + Class[] argTypes = ctor.argTypes; + + if (ctor.vararg) { + // marshall the explicit parameter + Object[] newArgs = new Object[argTypes.length]; + for (int i = 0; i < argTypes.length-1; i++) { + newArgs[i] = Context.jsToJava(args[i], argTypes[i]); + } + + Object varArgs; + + // Handle special situation where a single variable parameter + // is given and it is a Java or ECMA array. + if (args.length == argTypes.length && + (args[args.length-1] == null || + args[args.length-1] instanceof NativeArray || + args[args.length-1] instanceof NativeJavaArray)) + { + // convert the ECMA array into a native array + varArgs = Context.jsToJava(args[args.length-1], + argTypes[argTypes.length - 1]); + } else { + // marshall the variable parameter + Class componentType = argTypes[argTypes.length - 1]. + getComponentType(); + varArgs = Array.newInstance(componentType, + args.length - argTypes.length + 1); + for (int i=0; i < Array.getLength(varArgs); i++) { + Object value = Context.jsToJava(args[argTypes.length-1 + i], + componentType); + Array.set(varArgs, i, value); + } + } + + // add varargs + newArgs[argTypes.length-1] = varArgs; + // replace the original args with the new one + args = newArgs; + } else { + Object[] origArgs = args; + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + Object x = Context.jsToJava(arg, argTypes[i]); + if (x != arg) { + if (args == origArgs) { + args = origArgs.clone(); + } + args[i] = x; + } + } + } + + Object instance = ctor.newInstance(args); + // we need to force this to be wrapped, because construct _has_ + // to return a scriptable + return cx.getWrapFactory().wrapNewObject(cx, topLevel, instance); + } + + public String toString() { + return "[JavaClass " + getClassObject().getName() + "]"; + } + + /** + * Determines if prototype is a wrapped Java object and performs + * a Java "instanceof". + * Exception: if value is an instance of NativeJavaClass, it isn't + * considered an instance of the Java class; this forestalls any + * name conflicts between java.lang.Class's methods and the + * static methods exposed by a JavaNativeClass. + */ + public boolean hasInstance(Scriptable value) { + + if (value instanceof Wrapper && + !(value instanceof NativeJavaClass)) { + Object instance = ((Wrapper)value).unwrap(); + + return getClassObject().isInstance(instance); + } + + // value wasn't something we understand + return false; + } + + private static Class findNestedClass(Class parentClass, String name) { + String nestedClassName = parentClass.getName() + '$' + name; + ClassLoader loader = parentClass.getClassLoader(); + if (loader == null) { + // ALERT: if loader is null, nested class should be loaded + // via system class loader which can be different from the + // loader that brought Rhino classes that Class.forName() would + // use, but ClassLoader.getSystemClassLoader() is Java 2 only + return Kit.classOrNull(nestedClassName); + } else { + return Kit.classOrNull(loader, nestedClassName); + } + } + + private Hashtable staticFieldAndMethods; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaConstructor.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaConstructor.java new file mode 100644 index 0000000..530bf81 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaConstructor.java @@ -0,0 +1,85 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * 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; + +/** + * This class reflects a single Java constructor into the JavaScript + * environment. It satisfies a request for an overloaded constructor, + * as introduced in LiveConnect 3. + * All NativeJavaConstructors behave as JSRef `bound' methods, in that they + * always construct the same NativeJavaClass regardless of any reparenting + * that may occur. + * + * @author Frank Mitchell + * @see NativeJavaMethod + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaConstructor extends BaseFunction +{ + static final long serialVersionUID = -8149253217482668463L; + + MemberBox ctor; + + public NativeJavaConstructor(MemberBox ctor) + { + this.ctor = ctor; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return NativeJavaClass.constructSpecific(cx, scope, args, ctor); + } + + public String getFunctionName() + { + String sig = JavaMembers.liveConnectSignature(ctor.argTypes); + return "<init>".concat(sig); + } + + public String toString() + { + return "[JavaConstructor " + ctor.getName() + "]"; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaMethod.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaMethod.java new file mode 100644 index 0000000..eb66f40 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaMethod.java @@ -0,0 +1,576 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * Ulrike Mueller <umueller@demandware.com> + * + * 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.*; + +/** + * This class reflects Java methods into the JavaScript environment and + * handles overloading of methods. + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaMethod extends BaseFunction +{ + static final long serialVersionUID = -3440381785576412928L; + + NativeJavaMethod(MemberBox[] methods) + { + this.functionName = methods[0].getName(); + this.methods = methods; + } + + NativeJavaMethod(MemberBox method, String name) + { + this.functionName = name; + this.methods = new MemberBox[] { method }; + } + + public NativeJavaMethod(Method method, String name) + { + this(new MemberBox(method), name); + } + + public String getFunctionName() + { + return functionName; + } + + static String scriptSignature(Object[] values) + { + StringBuffer sig = new StringBuffer(); + for (int i = 0; i != values.length; ++i) { + Object value = values[i]; + + String s; + if (value == null) { + s = "null"; + } else if (value instanceof Boolean) { + s = "boolean"; + } else if (value instanceof String) { + s = "string"; + } else if (value instanceof Number) { + s = "number"; + } else if (value instanceof Scriptable) { + if (value instanceof Undefined) { + s = "undefined"; + } else if (value instanceof Wrapper) { + Object wrapped = ((Wrapper)value).unwrap(); + s = wrapped.getClass().getName(); + } else if (value instanceof Function) { + s = "function"; + } else { + s = "object"; + } + } else { + s = JavaMembers.javaSignature(value.getClass()); + } + + if (i != 0) { + sig.append(','); + } + sig.append(s); + } + return sig.toString(); + } + + String decompile(int indent, int flags) + { + StringBuffer sb = new StringBuffer(); + boolean justbody = (0 != (flags & Decompiler.ONLY_BODY_FLAG)); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() {"); + } + sb.append("/*\n"); + sb.append(toString()); + sb.append(justbody ? "*/\n" : "*/}\n"); + return sb.toString(); + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + for (int i = 0, N = methods.length; i != N; ++i) { + Method method = methods[i].method(); + sb.append(JavaMembers.javaSignature(method.getReturnType())); + sb.append(' '); + sb.append(method.getName()); + sb.append(JavaMembers.liveConnectSignature(methods[i].argTypes)); + sb.append('\n'); + } + return sb.toString(); + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + // Find a method that matches the types given. + if (methods.length == 0) { + throw new RuntimeException("No methods defined for call"); + } + + int index = findFunction(cx, methods, args); + if (index < 0) { + Class c = methods[0].method().getDeclaringClass(); + String sig = c.getName() + '.' + getFunctionName() + '(' + + scriptSignature(args) + ')'; + throw Context.reportRuntimeError1("msg.java.no_such_method", sig); + } + + MemberBox meth = methods[index]; + Class[] argTypes = meth.argTypes; + + if (meth.vararg) { + // marshall the explicit parameters + Object[] newArgs = new Object[argTypes.length]; + for (int i = 0; i < argTypes.length-1; i++) { + newArgs[i] = Context.jsToJava(args[i], argTypes[i]); + } + + Object varArgs; + + // Handle special situation where a single variable parameter + // is given and it is a Java or ECMA array or is null. + if (args.length == argTypes.length && + (args[args.length-1] == null || + args[args.length-1] instanceof NativeArray || + args[args.length-1] instanceof NativeJavaArray)) + { + // convert the ECMA array into a native array + varArgs = Context.jsToJava(args[args.length-1], + argTypes[argTypes.length - 1]); + } else { + // marshall the variable parameters + Class componentType = argTypes[argTypes.length - 1]. + getComponentType(); + varArgs = Array.newInstance(componentType, + args.length - argTypes.length + 1); + for (int i = 0; i < Array.getLength(varArgs); i++) { + Object value = Context.jsToJava(args[argTypes.length-1 + i], + componentType); + Array.set(varArgs, i, value); + } + } + + // add varargs + newArgs[argTypes.length-1] = varArgs; + // replace the original args with the new one + args = newArgs; + } else { + // First, we marshall the args. + Object[] origArgs = args; + for (int i = 0; i < args.length; i++) { + Object arg = args[i]; + Object coerced = Context.jsToJava(arg, argTypes[i]); + if (coerced != arg) { + if (origArgs == args) { + args = args.clone(); + } + args[i] = coerced; + } + } + } + Object javaObject; + if (meth.isStatic()) { + javaObject = null; // don't need an object + } else { + Scriptable o = thisObj; + Class c = meth.getDeclaringClass(); + for (;;) { + if (o == null) { + throw Context.reportRuntimeError3( + "msg.nonjava.method", getFunctionName(), + ScriptRuntime.toString(thisObj), c.getName()); + } + if (o instanceof Wrapper) { + javaObject = ((Wrapper)o).unwrap(); + if (c.isInstance(javaObject)) { + break; + } + } + o = o.getPrototype(); + } + } + if (debug) { + printDebug("Calling ", meth, args); + } + + Object retval = meth.invoke(javaObject, args); + Class staticType = meth.method().getReturnType(); + + if (debug) { + Class actualType = (retval == null) ? null + : retval.getClass(); + System.err.println(" ----- Returned " + retval + + " actual = " + actualType + + " expect = " + staticType); + } + + Object wrapped = cx.getWrapFactory().wrap(cx, scope, + retval, staticType); + if (debug) { + Class actualType = (wrapped == null) ? null + : wrapped.getClass(); + System.err.println(" ----- Wrapped as " + wrapped + + " class = " + actualType); + } + + if (wrapped == null && staticType == Void.TYPE) { + wrapped = Undefined.instance; + } + return wrapped; + } + + /** + * Find the index of the correct function to call given the set of methods + * or constructors and the arguments. + * If no function can be found to call, return -1. + */ + static int findFunction(Context cx, + MemberBox[] methodsOrCtors, Object[] args) + { + if (methodsOrCtors.length == 0) { + return -1; + } else if (methodsOrCtors.length == 1) { + MemberBox member = methodsOrCtors[0]; + Class[] argTypes = member.argTypes; + int alength = argTypes.length; + + if (member.vararg) { + alength--; + if ( alength > args.length) { + return -1; + } + } else { + if (alength != args.length) { + return -1; + } + } + for (int j = 0; j != alength; ++j) { + if (!NativeJavaObject.canConvert(args[j], argTypes[j])) { + if (debug) printDebug("Rejecting (args can't convert) ", + member, args); + return -1; + } + } + if (debug) printDebug("Found ", member, args); + return 0; + } + + int firstBestFit = -1; + int[] extraBestFits = null; + int extraBestFitsCount = 0; + + search: + for (int i = 0; i < methodsOrCtors.length; i++) { + MemberBox member = methodsOrCtors[i]; + Class[] argTypes = member.argTypes; + int alength = argTypes.length; + if (member.vararg) { + alength--; + if ( alength > args.length) { + continue search; + } + } else { + if (alength != args.length) { + continue search; + } + } + for (int j = 0; j < alength; j++) { + if (!NativeJavaObject.canConvert(args[j], argTypes[j])) { + if (debug) printDebug("Rejecting (args can't convert) ", + member, args); + continue search; + } + } + if (firstBestFit < 0) { + if (debug) printDebug("Found first applicable ", member, args); + firstBestFit = i; + } else { + // Compare with all currently fit methods. + // The loop starts from -1 denoting firstBestFit and proceed + // until extraBestFitsCount to avoid extraBestFits allocation + // in the most common case of no ambiguity + int betterCount = 0; // number of times member was prefered over + // best fits + int worseCount = 0; // number of times best fits were prefered + // over member + for (int j = -1; j != extraBestFitsCount; ++j) { + int bestFitIndex; + if (j == -1) { + bestFitIndex = firstBestFit; + } else { + bestFitIndex = extraBestFits[j]; + } + MemberBox bestFit = methodsOrCtors[bestFitIndex]; + if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS) && + (bestFit.member().getModifiers() & Modifier.PUBLIC) != + (member.member().getModifiers() & Modifier.PUBLIC)) + { + // When FEATURE_ENHANCED_JAVA_ACCESS gives us access + // to non-public members, continue to prefer public + // methods in overloading + if ((bestFit.member().getModifiers() & Modifier.PUBLIC) == 0) + ++betterCount; + else + ++worseCount; + } else { + int preference = preferSignature(args, argTypes, + member.vararg, + bestFit.argTypes, + bestFit.vararg ); + if (preference == PREFERENCE_AMBIGUOUS) { + break; + } else if (preference == PREFERENCE_FIRST_ARG) { + ++betterCount; + } else if (preference == PREFERENCE_SECOND_ARG) { + ++worseCount; + } else { + if (preference != PREFERENCE_EQUAL) Kit.codeBug(); + // This should not happen in theory + // but on some JVMs, Class.getMethods will return all + // static methods of the class heirarchy, even if + // a derived class's parameters match exactly. + // We want to call the dervied class's method. + if (bestFit.isStatic() + && bestFit.getDeclaringClass().isAssignableFrom( + member.getDeclaringClass())) + { + // On some JVMs, Class.getMethods will return all + // static methods of the class heirarchy, even if + // a derived class's parameters match exactly. + // We want to call the dervied class's method. + if (debug) printDebug( + "Substituting (overridden static)", + member, args); + if (j == -1) { + firstBestFit = i; + } else { + extraBestFits[j] = i; + } + } else { + if (debug) printDebug( + "Ignoring same signature member ", + member, args); + } + continue search; + } + } + } + if (betterCount == 1 + extraBestFitsCount) { + // member was prefered over all best fits + if (debug) printDebug( + "New first applicable ", member, args); + firstBestFit = i; + extraBestFitsCount = 0; + } else if (worseCount == 1 + extraBestFitsCount) { + // all best fits were prefered over member, ignore it + if (debug) printDebug( + "Rejecting (all current bests better) ", member, args); + } else { + // some ambiguity was present, add member to best fit set + if (debug) printDebug( + "Added to best fit set ", member, args); + if (extraBestFits == null) { + // Allocate maximum possible array + extraBestFits = new int[methodsOrCtors.length - 1]; + } + extraBestFits[extraBestFitsCount] = i; + ++extraBestFitsCount; + } + } + } + + if (firstBestFit < 0) { + // Nothing was found + return -1; + } else if (extraBestFitsCount == 0) { + // single best fit + return firstBestFit; + } + + // report remaining ambiguity + StringBuffer buf = new StringBuffer(); + for (int j = -1; j != extraBestFitsCount; ++j) { + int bestFitIndex; + if (j == -1) { + bestFitIndex = firstBestFit; + } else { + bestFitIndex = extraBestFits[j]; + } + buf.append("\n "); + buf.append(methodsOrCtors[bestFitIndex].toJavaDeclaration()); + } + + MemberBox firstFitMember = methodsOrCtors[firstBestFit]; + String memberName = firstFitMember.getName(); + String memberClass = firstFitMember.getDeclaringClass().getName(); + + if (methodsOrCtors[0].isMethod()) { + throw Context.reportRuntimeError3( + "msg.constructor.ambiguous", + memberName, scriptSignature(args), buf.toString()); + } else { + throw Context.reportRuntimeError4( + "msg.method.ambiguous", memberClass, + memberName, scriptSignature(args), buf.toString()); + } + } + + /** Types are equal */ + private static final int PREFERENCE_EQUAL = 0; + private static final int PREFERENCE_FIRST_ARG = 1; + private static final int PREFERENCE_SECOND_ARG = 2; + /** No clear "easy" conversion */ + private static final int PREFERENCE_AMBIGUOUS = 3; + + /** + * Determine which of two signatures is the closer fit. + * Returns one of PREFERENCE_EQUAL, PREFERENCE_FIRST_ARG, + * PREFERENCE_SECOND_ARG, or PREFERENCE_AMBIGUOUS. + */ + private static int preferSignature(Object[] args, + Class[] sig1, + boolean vararg1, + Class[] sig2, + boolean vararg2 ) + { + // TODO: This test is pretty primitive. It bascially prefers + // a matching no vararg method over a vararg method independent + // of the type conversion cost. This can lead to unexpected results. + int alength = args.length; + if (!vararg1 && vararg2) { + // prefer the no vararg signature + return PREFERENCE_FIRST_ARG; + } else if (vararg1 && !vararg2) { + // prefer the no vararg signature + return PREFERENCE_SECOND_ARG; + } else if (vararg1 && vararg2) { + if (sig1.length < sig2.length) { + // prefer the signature with more explicit types + return PREFERENCE_SECOND_ARG; + } else if (sig1.length > sig2.length) { + // prefer the signature with more explicit types + return PREFERENCE_FIRST_ARG; + } else { + // Both are varargs and have the same length, so make the + // decision with the explicit args. + alength = Math.min(args.length, sig1.length-1); + } + } + + int totalPreference = 0; + for (int j = 0; j < alength; j++) { + Class type1 = sig1[j]; + Class type2 = sig2[j]; + if (type1 == type2) { + continue; + } + Object arg = args[j]; + + // Determine which of type1, type2 is easier to convert from arg. + + int rank1 = NativeJavaObject.getConversionWeight(arg, type1); + int rank2 = NativeJavaObject.getConversionWeight(arg, type2); + + int preference; + if (rank1 < rank2) { + preference = PREFERENCE_FIRST_ARG; + } else if (rank1 > rank2) { + preference = PREFERENCE_SECOND_ARG; + } else { + // Equal ranks + if (rank1 == NativeJavaObject.CONVERSION_NONTRIVIAL) { + if (type1.isAssignableFrom(type2)) { + preference = PREFERENCE_SECOND_ARG; + } else if (type2.isAssignableFrom(type1)) { + preference = PREFERENCE_FIRST_ARG; + } else { + preference = PREFERENCE_AMBIGUOUS; + } + } else { + preference = PREFERENCE_AMBIGUOUS; + } + } + + totalPreference |= preference; + + if (totalPreference == PREFERENCE_AMBIGUOUS) { + break; + } + } + return totalPreference; + } + + + private static final boolean debug = false; + + private static void printDebug(String msg, MemberBox member, + Object[] args) + { + if (debug) { + StringBuffer sb = new StringBuffer(); + sb.append(" ----- "); + sb.append(msg); + sb.append(member.getDeclaringClass().getName()); + sb.append('.'); + if (member.isMethod()) { + sb.append(member.getName()); + } + sb.append(JavaMembers.liveConnectSignature(member.argTypes)); + sb.append(" for arguments ("); + sb.append(scriptSignature(args)); + sb.append(')'); + System.out.println(sb); + } + } + + MemberBox[] methods; + private String functionName; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaObject.java new file mode 100644 index 0000000..3d27852 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaObject.java @@ -0,0 +1,1002 @@ +/* -*- 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): + * Norris Boyd + * Igor Bukanov + * Frank Mitchell + * Mike Shaver + * Kemal Bayram + * + * 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.io.*; +import java.lang.reflect.*; +import java.util.Hashtable; +import java.util.Date; + +/** + * This class reflects non-Array Java objects into the JavaScript environment. It + * reflect fields directly, and uses NativeJavaMethod objects to reflect (possibly + * overloaded) methods.<p> + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaPackage + * @see NativeJavaClass + */ + +public class NativeJavaObject implements Scriptable, Wrapper, Serializable +{ + static final long serialVersionUID = -6948590651130498591L; + + public NativeJavaObject() { } + + public NativeJavaObject(Scriptable scope, Object javaObject, + Class staticType) + { + this(scope, javaObject, staticType, false); + } + + public NativeJavaObject(Scriptable scope, Object javaObject, + Class staticType, boolean isAdapter) + { + this.parent = ScriptableObject.getVeryTopLevelScope(scope); // APPJET + this.javaObject = javaObject; + this.staticType = staticType; + this.isAdapter = isAdapter; + initMembers(); + } + + protected void initMembers() { + Class dynamicType; + if (javaObject != null) { + dynamicType = javaObject.getClass(); + } else { + dynamicType = staticType; + } + members = JavaMembers.lookupClass(parent, dynamicType, staticType, + isAdapter); + fieldAndMethods + = members.getFieldAndMethodsObjects(this, javaObject, false); + } + + public boolean has(String name, Scriptable start) { + return members.has(name, false); + } + + public boolean has(int index, Scriptable start) { + return false; + } + + public Object get(String name, Scriptable start) { + if (fieldAndMethods != null) { + Object result = fieldAndMethods.get(name); + if (result != null) { + return result; + } + } + // TODO: passing 'this' as the scope is bogus since it has + // no parent scope + return members.get(this, name, javaObject, false); + } + + public Object get(int index, Scriptable start) { + throw members.reportMemberNotFound(Integer.toString(index)); + } + + public void put(String name, Scriptable start, Object value) { + // We could be asked to modify the value of a property in the + // prototype. Since we can't add a property to a Java object, + // we modify it in the prototype rather than copy it down. + if (prototype == null || members.has(name, false)) + members.put(this, name, javaObject, value, false); + else + prototype.put(name, prototype, value); + } + + public void put(int index, Scriptable start, Object value) { + throw members.reportMemberNotFound(Integer.toString(index)); + } + + public boolean hasInstance(Scriptable value) { + // This is an instance of a Java class, so always return false + return false; + } + + public void delete(String name) { + } + + public void delete(int index) { + } + + public Scriptable getPrototype() { + if (prototype == null && javaObject instanceof String) { + return ScriptableObject.getClassPrototype(parent, "String"); + } + return prototype; + } + + /** + * Sets the prototype of the object. + */ + public void setPrototype(Scriptable m) { + prototype = m; + } + + /** + * Returns the parent (enclosing) scope of the object. + */ + public Scriptable getParentScope() { + return parent; + } + + /** + * Sets the parent (enclosing) scope of the object. + */ + public void setParentScope(Scriptable m) { + parent = m; + } + + public Object[] getIds() { + return members.getIds(false); + } + +/** +@deprecated Use {@link Context#getWrapFactory()} together with calling {@link +WrapFactory#wrap(Context, Scriptable, Object, Class)} +*/ + public static Object wrap(Scriptable scope, Object obj, Class staticType) { + + Context cx = Context.getContext(); + return cx.getWrapFactory().wrap(cx, scope, obj, staticType); + } + + public Object unwrap() { + return javaObject; + } + + public String getClassName() { + return "JavaObject"; + } + + public Object getDefaultValue(Class hint) + { + Object value; + if (hint == null) { + if (javaObject instanceof Boolean) { + hint = ScriptRuntime.BooleanClass; + } + } + if (hint == null || hint == ScriptRuntime.StringClass) { + value = javaObject.toString(); + } else { + String converterName; + if (hint == ScriptRuntime.BooleanClass) { + converterName = "booleanValue"; + } else if (hint == ScriptRuntime.NumberClass) { + converterName = "doubleValue"; + } else { + throw Context.reportRuntimeError0("msg.default.value"); + } + Object converterObject = get(converterName, this); + if (converterObject instanceof Function) { + Function f = (Function)converterObject; + value = f.call(Context.getContext(), f.getParentScope(), + this, ScriptRuntime.emptyArgs); + } else { + if (hint == ScriptRuntime.NumberClass + && javaObject instanceof Boolean) + { + boolean b = ((Boolean)javaObject).booleanValue(); + value = ScriptRuntime.wrapNumber(b ? 1.0 : 0.0); + } else { + value = javaObject.toString(); + } + } + } + return value; + } + + /** + * Determine whether we can/should convert between the given type and the + * desired one. This should be superceded by a conversion-cost calculation + * function, but for now I'll hide behind precedent. + */ + public static boolean canConvert(Object fromObj, Class to) { + int weight = getConversionWeight(fromObj, to); + + return (weight < CONVERSION_NONE); + } + + private static final int JSTYPE_UNDEFINED = 0; // undefined type + private static final int JSTYPE_NULL = 1; // null + private static final int JSTYPE_BOOLEAN = 2; // boolean + private static final int JSTYPE_NUMBER = 3; // number + private static final int JSTYPE_STRING = 4; // string + private static final int JSTYPE_JAVA_CLASS = 5; // JavaClass + private static final int JSTYPE_JAVA_OBJECT = 6; // JavaObject + private static final int JSTYPE_JAVA_ARRAY = 7; // JavaArray + private static final int JSTYPE_OBJECT = 8; // Scriptable + + static final byte CONVERSION_TRIVIAL = 1; + static final byte CONVERSION_NONTRIVIAL = 0; + static final byte CONVERSION_NONE = 99; + + /** + * Derive a ranking based on how "natural" the conversion is. + * The special value CONVERSION_NONE means no conversion is possible, + * and CONVERSION_NONTRIVIAL signals that more type conformance testing + * is required. + * Based on + * <a href="http://www.mozilla.org/js/liveconnect/lc3_method_overloading.html"> + * "preferred method conversions" from Live Connect 3</a> + */ + static int getConversionWeight(Object fromObj, Class to) { + int fromCode = getJSTypeCode(fromObj); + + switch (fromCode) { + + case JSTYPE_UNDEFINED: + if (to == ScriptRuntime.StringClass || + to == ScriptRuntime.ObjectClass) { + return 1; + } + break; + + case JSTYPE_NULL: + if (!to.isPrimitive()) { + return 1; + } + break; + + case JSTYPE_BOOLEAN: + // "boolean" is #1 + if (to == Boolean.TYPE) { + return 1; + } + else if (to == ScriptRuntime.BooleanClass) { + return 2; + } + else if (to == ScriptRuntime.ObjectClass) { + return 3; + } + else if (to == ScriptRuntime.StringClass) { + return 4; + } + break; + + case JSTYPE_NUMBER: + if (to.isPrimitive()) { + if (to == Double.TYPE) { + return 1; + } + else if (to != Boolean.TYPE) { + return 1 + getSizeRank(to); + } + } + else { + if (to == ScriptRuntime.StringClass) { + // native numbers are #1-8 + return 9; + } + else if (to == ScriptRuntime.ObjectClass) { + return 10; + } + else if (ScriptRuntime.NumberClass.isAssignableFrom(to)) { + // "double" is #1 + return 2; + } + } + break; + + case JSTYPE_STRING: + if (to == ScriptRuntime.StringClass) { + return 1; + } + else if (to.isInstance(fromObj)) { + return 2; + } + else if (to.isPrimitive()) { + if (to == Character.TYPE) { + return 3; + } else if (to != Boolean.TYPE) { + return 4; + } + } + break; + + case JSTYPE_JAVA_CLASS: + if (to == ScriptRuntime.ClassClass) { + return 1; + } + else if (to == ScriptRuntime.ObjectClass) { + return 3; + } + else if (to == ScriptRuntime.StringClass) { + return 4; + } + break; + + case JSTYPE_JAVA_OBJECT: + case JSTYPE_JAVA_ARRAY: + Object javaObj = fromObj; + if (javaObj instanceof Wrapper) { + javaObj = ((Wrapper)javaObj).unwrap(); + } + if (to.isInstance(javaObj)) { + return CONVERSION_NONTRIVIAL; + } + if (to == ScriptRuntime.StringClass) { + return 2; + } + else if (to.isPrimitive() && to != Boolean.TYPE) { + return (fromCode == JSTYPE_JAVA_ARRAY) + ? CONVERSION_NONE : 2 + getSizeRank(to); + } + break; + + case JSTYPE_OBJECT: + // Other objects takes #1-#3 spots + if (to == fromObj.getClass()) { + // No conversion required + return 1; + } + if (to.isArray()) { + if (fromObj instanceof NativeArray) { + // This is a native array conversion to a java array + // Array conversions are all equal, and preferable to object + // and string conversion, per LC3. + return 1; + } + } + else if (to == ScriptRuntime.ObjectClass) { + return 2; + } + else if (to == ScriptRuntime.StringClass) { + return 3; + } + else if (to == ScriptRuntime.DateClass) { + if (fromObj instanceof NativeDate) { + // This is a native date to java date conversion + return 1; + } + } + else if (to.isInterface()) { + if (fromObj instanceof Function) { + // See comments in coerceType + if (to.getMethods().length == 1) { + return 1; + } + } + return 11; + } + else if (to.isPrimitive() && to != Boolean.TYPE) { + return 3 + getSizeRank(to); + } + break; + } + + return CONVERSION_NONE; + } + + static int getSizeRank(Class aType) { + if (aType == Double.TYPE) { + return 1; + } + else if (aType == Float.TYPE) { + return 2; + } + else if (aType == Long.TYPE) { + return 3; + } + else if (aType == Integer.TYPE) { + return 4; + } + else if (aType == Short.TYPE) { + return 5; + } + else if (aType == Character.TYPE) { + return 6; + } + else if (aType == Byte.TYPE) { + return 7; + } + else if (aType == Boolean.TYPE) { + return CONVERSION_NONE; + } + else { + return 8; + } + } + + private static int getJSTypeCode(Object value) { + if (value == null) { + return JSTYPE_NULL; + } + else if (value == Undefined.instance) { + return JSTYPE_UNDEFINED; + } + else if (value instanceof String) { + return JSTYPE_STRING; + } + else if (value instanceof Number) { + return JSTYPE_NUMBER; + } + else if (value instanceof Boolean) { + return JSTYPE_BOOLEAN; + } + else if (value instanceof Scriptable) { + if (value instanceof NativeJavaClass) { + return JSTYPE_JAVA_CLASS; + } + else if (value instanceof NativeJavaArray) { + return JSTYPE_JAVA_ARRAY; + } + else if (value instanceof Wrapper) { + return JSTYPE_JAVA_OBJECT; + } + else { + return JSTYPE_OBJECT; + } + } + else if (value instanceof Class) { + return JSTYPE_JAVA_CLASS; + } + else { + Class valueClass = value.getClass(); + if (valueClass.isArray()) { + return JSTYPE_JAVA_ARRAY; + } + else { + return JSTYPE_JAVA_OBJECT; + } + } + } + + /** + * Not intended for public use. Callers should use the + * public API Context.toType. + * @deprecated as of 1.5 Release 4 + * @see org.mozilla.javascript.Context#jsToJava(Object, Class) + */ + public static Object coerceType(Class type, Object value) + { + return coerceTypeImpl(type, value); + } + + /** + * Type-munging for field setting and method invocation. + * Conforms to LC3 specification + */ + static Object coerceTypeImpl(Class type, Object value) + { + if (value != null && value.getClass() == type) { + return value; + } + + switch (getJSTypeCode(value)) { + + case JSTYPE_NULL: + // raise error if type.isPrimitive() + if (type.isPrimitive()) { + reportConversionError(value, type); + } + return null; + + case JSTYPE_UNDEFINED: + if (type == ScriptRuntime.StringClass || + type == ScriptRuntime.ObjectClass) { + return "undefined"; + } + else { + reportConversionError("undefined", type); + } + break; + + case JSTYPE_BOOLEAN: + // Under LC3, only JS Booleans can be coerced into a Boolean value + if (type == Boolean.TYPE || + type == ScriptRuntime.BooleanClass || + type == ScriptRuntime.ObjectClass) { + return value; + } + else if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_NUMBER: + if (type == ScriptRuntime.StringClass) { + return ScriptRuntime.toString(value); + } + else if (type == ScriptRuntime.ObjectClass) { + return coerceToNumber(Double.TYPE, value); + } + else if ((type.isPrimitive() && type != Boolean.TYPE) || + ScriptRuntime.NumberClass.isAssignableFrom(type)) { + return coerceToNumber(type, value); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_STRING: + if (type == ScriptRuntime.StringClass || type.isInstance(value)) { + return value; + } + else if (type == Character.TYPE + || type == ScriptRuntime.CharacterClass) + { + // Special case for converting a single char string to a + // character + // Placed here because it applies *only* to JS strings, + // not other JS objects converted to strings + if (((String)value).length() == 1) { + return new Character(((String)value).charAt(0)); + } + else { + return coerceToNumber(type, value); + } + } + else if ((type.isPrimitive() && type != Boolean.TYPE) + || ScriptRuntime.NumberClass.isAssignableFrom(type)) + { + return coerceToNumber(type, value); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_JAVA_CLASS: + if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + } + + if (type == ScriptRuntime.ClassClass || + type == ScriptRuntime.ObjectClass) { + return value; + } + else if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + reportConversionError(value, type); + } + break; + + case JSTYPE_JAVA_OBJECT: + case JSTYPE_JAVA_ARRAY: + if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + } + if (type.isPrimitive()) { + if (type == Boolean.TYPE) { + reportConversionError(value, type); + } + return coerceToNumber(type, value); + } + else { + if (type == ScriptRuntime.StringClass) { + return value.toString(); + } + else { + if (type.isInstance(value)) { + return value; + } + else { + reportConversionError(value, type); + } + } + } + break; + + case JSTYPE_OBJECT: + if (type == ScriptRuntime.StringClass) { + return ScriptRuntime.toString(value); + } + else if (type.isPrimitive()) { + if (type == Boolean.TYPE) { + reportConversionError(value, type); + } + return coerceToNumber(type, value); + } + else if (type.isInstance(value)) { + return value; + } + else if (type == ScriptRuntime.DateClass + && value instanceof NativeDate) + { + double time = ((NativeDate)value).getJSTimeValue(); + // XXX: This will replace NaN by 0 + return new Date((long)time); + } + else if (type.isArray() && value instanceof NativeArray) { + // Make a new java array, and coerce the JS array components + // to the target (component) type. + NativeArray array = (NativeArray) value; + long length = array.getLength(); + Class arrayType = type.getComponentType(); + Object Result = Array.newInstance(arrayType, (int)length); + for (int i = 0 ; i < length ; ++i) { + try { + Array.set(Result, i, coerceType(arrayType, + array.get(i, array))); + } + catch (EvaluatorException ee) { + reportConversionError(value, type); + } + } + + return Result; + } + else if (value instanceof Wrapper) { + value = ((Wrapper)value).unwrap(); + if (type.isInstance(value)) + return value; + reportConversionError(value, type); + } + else if (type.isInterface() && value instanceof Callable) { + // Try to use function as implementation of Java interface. + // + // XXX: Curently only instances of ScriptableObject are + // supported since the resulting interface proxies should + // be reused next time conversion is made and generic + // Callable has no storage for it. Weak references can + // address it but for now use this restriction. + if (value instanceof ScriptableObject) { + ScriptableObject so = (ScriptableObject)value; + Object key = Kit.makeHashKeyFromPair( + COERCED_INTERFACE_KEY, type); + Object old = so.getAssociatedValue(key); + if (old != null) { + // Function was already wrapped + return old; + } + Context cx = Context.getContext(); + Object glue + = InterfaceAdapter.create(cx, type, (Callable)value); + // Store for later retrival + glue = so.associateValue(key, glue); + return glue; + } + reportConversionError(value, type); + } else { + reportConversionError(value, type); + } + break; + } + + return value; + } + + private static Object coerceToNumber(Class type, Object value) + { + Class valueClass = value.getClass(); + + // Character + if (type == Character.TYPE || type == ScriptRuntime.CharacterClass) { + if (valueClass == ScriptRuntime.CharacterClass) { + return value; + } + return new Character((char)toInteger(value, + ScriptRuntime.CharacterClass, + Character.MIN_VALUE, + Character.MAX_VALUE)); + } + + // Double, Float + if (type == ScriptRuntime.ObjectClass || + type == ScriptRuntime.DoubleClass || type == Double.TYPE) { + return valueClass == ScriptRuntime.DoubleClass + ? value + : new Double(toDouble(value)); + } + + if (type == ScriptRuntime.FloatClass || type == Float.TYPE) { + if (valueClass == ScriptRuntime.FloatClass) { + return value; + } + else { + double number = toDouble(value); + if (Double.isInfinite(number) || Double.isNaN(number) + || number == 0.0) { + return new Float((float)number); + } + else { + double absNumber = Math.abs(number); + if (absNumber < Float.MIN_VALUE) { + return new Float((number > 0.0) ? +0.0 : -0.0); + } + else if (absNumber > Float.MAX_VALUE) { + return new Float((number > 0.0) ? + Float.POSITIVE_INFINITY : + Float.NEGATIVE_INFINITY); + } + else { + return new Float((float)number); + } + } + } + } + + // Integer, Long, Short, Byte + if (type == ScriptRuntime.IntegerClass || type == Integer.TYPE) { + if (valueClass == ScriptRuntime.IntegerClass) { + return value; + } + else { + return new Integer((int)toInteger(value, + ScriptRuntime.IntegerClass, + Integer.MIN_VALUE, + Integer.MAX_VALUE)); + } + } + + if (type == ScriptRuntime.LongClass || type == Long.TYPE) { + if (valueClass == ScriptRuntime.LongClass) { + return value; + } else { + /* Long values cannot be expressed exactly in doubles. + * We thus use the largest and smallest double value that + * has a value expressible as a long value. We build these + * numerical values from their hexidecimal representations + * to avoid any problems caused by attempting to parse a + * decimal representation. + */ + final double max = Double.longBitsToDouble(0x43dfffffffffffffL); + final double min = Double.longBitsToDouble(0xc3e0000000000000L); + return new Long(toInteger(value, + ScriptRuntime.LongClass, + min, + max)); + } + } + + if (type == ScriptRuntime.ShortClass || type == Short.TYPE) { + if (valueClass == ScriptRuntime.ShortClass) { + return value; + } + else { + return new Short((short)toInteger(value, + ScriptRuntime.ShortClass, + Short.MIN_VALUE, + Short.MAX_VALUE)); + } + } + + if (type == ScriptRuntime.ByteClass || type == Byte.TYPE) { + if (valueClass == ScriptRuntime.ByteClass) { + return value; + } + else { + return new Byte((byte)toInteger(value, + ScriptRuntime.ByteClass, + Byte.MIN_VALUE, + Byte.MAX_VALUE)); + } + } + + return new Double(toDouble(value)); + } + + + private static double toDouble(Object value) + { + if (value instanceof Number) { + return ((Number)value).doubleValue(); + } + else if (value instanceof String) { + return ScriptRuntime.toNumber((String)value); + } + else if (value instanceof Scriptable) { + if (value instanceof Wrapper) { + // XXX: optimize tail-recursion? + return toDouble(((Wrapper)value).unwrap()); + } + else { + return ScriptRuntime.toNumber(value); + } + } + else { + Method meth; + try { + meth = value.getClass().getMethod("doubleValue", + (Class [])null); + } + catch (NoSuchMethodException e) { + meth = null; + } + catch (SecurityException e) { + meth = null; + } + if (meth != null) { + try { + return ((Number)meth.invoke(value, + (Object [])null)).doubleValue(); + } + catch (IllegalAccessException e) { + // XXX: ignore, or error message? + reportConversionError(value, Double.TYPE); + } + catch (InvocationTargetException e) { + // XXX: ignore, or error message? + reportConversionError(value, Double.TYPE); + } + } + return ScriptRuntime.toNumber(value.toString()); + } + } + + private static long toInteger(Object value, Class type, + double min, double max) + { + double d = toDouble(value); + + if (Double.isInfinite(d) || Double.isNaN(d)) { + // Convert to string first, for more readable message + reportConversionError(ScriptRuntime.toString(value), type); + } + + if (d > 0.0) { + d = Math.floor(d); + } + else { + d = Math.ceil(d); + } + + if (d < min || d > max) { + // Convert to string first, for more readable message + reportConversionError(ScriptRuntime.toString(value), type); + } + return (long)d; + } + + static void reportConversionError(Object value, Class type) + { + // It uses String.valueOf(value), not value.toString() since + // value can be null, bug 282447. + throw Context.reportRuntimeError2( + "msg.conversion.not.allowed", + String.valueOf(value), + JavaMembers.javaSignature(type)); + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + + out.writeBoolean(isAdapter); + if (isAdapter) { + if (adapter_writeAdapterObject == null) { + throw new IOException(); + } + Object[] args = { javaObject, out }; + try { + adapter_writeAdapterObject.invoke(null, args); + } catch (Exception ex) { + throw new IOException(); + } + } else { + out.writeObject(javaObject); + } + + if (staticType != null) { + out.writeObject(staticType.getClass().getName()); + } else { + out.writeObject(null); + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + isAdapter = in.readBoolean(); + if (isAdapter) { + if (adapter_readAdapterObject == null) + throw new ClassNotFoundException(); + Object[] args = { this, in }; + try { + javaObject = adapter_readAdapterObject.invoke(null, args); + } catch (Exception ex) { + throw new IOException(); + } + } else { + javaObject = in.readObject(); + } + + String className = (String)in.readObject(); + if (className != null) { + staticType = Class.forName(className); + } else { + staticType = null; + } + + initMembers(); + } + + /** + * The prototype of this object. + */ + protected Scriptable prototype; + + /** + * The parent scope of this object. + */ + protected Scriptable parent; + + protected transient Object javaObject; + + protected transient Class staticType; + protected transient JavaMembers members; + private transient Hashtable fieldAndMethods; + private transient boolean isAdapter; + + private static final Object COERCED_INTERFACE_KEY = new Object(); + private static Method adapter_writeAdapterObject; + private static Method adapter_readAdapterObject; + + static { + // Reflection in java is verbose + Class[] sig2 = new Class[2]; + Class cl = Kit.classOrNull("org.mozilla.javascript.JavaAdapter"); + if (cl != null) { + try { + sig2[0] = ScriptRuntime.ObjectClass; + sig2[1] = Kit.classOrNull("java.io.ObjectOutputStream"); + adapter_writeAdapterObject = cl.getMethod("writeAdapterObject", + sig2); + + sig2[0] = ScriptRuntime.ScriptableClass; + sig2[1] = Kit.classOrNull("java.io.ObjectInputStream"); + adapter_readAdapterObject = cl.getMethod("readAdapterObject", + sig2); + + } catch (Exception ex) { + adapter_writeAdapterObject = null; + adapter_readAdapterObject = null; + } + } + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaPackage.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaPackage.java new file mode 100644 index 0000000..71f09f7 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaPackage.java @@ -0,0 +1,199 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * 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; + +/** + * This class reflects Java packages into the JavaScript environment. We + * lazily reflect classes and subpackages, and use a caching/sharing + * system to ensure that members reflected into one JavaPackage appear + * in all other references to the same package (as with Packages.java.lang + * and java.lang). + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaObject + * @see NativeJavaClass + */ + +public class NativeJavaPackage extends ScriptableObject +{ + static final long serialVersionUID = 7445054382212031523L; + + NativeJavaPackage(boolean internalUsage, + String packageName, ClassLoader classLoader) + { + this.packageName = packageName; + this.classLoader = classLoader; + } + + /** + * @deprecated NativeJavaPackage is an internal class, do not use + * it directly. + */ + public NativeJavaPackage(String packageName, ClassLoader classLoader) { + this(false, packageName, classLoader); + } + + /** + * @deprecated NativeJavaPackage is an internal class, do not use + * it directly. + */ + public NativeJavaPackage(String packageName) { + this(false, packageName, + Context.getCurrentContext().getApplicationClassLoader()); + } + + public String getClassName() { + return "JavaPackage"; + } + + public boolean has(String id, Scriptable start) { + return true; + } + + public boolean has(int index, Scriptable start) { + return false; + } + + public void put(String id, Scriptable start, Object value) { + // Can't add properties to Java packages. Sorry. + } + + public void put(int index, Scriptable start, Object value) { + throw Context.reportRuntimeError0("msg.pkg.int"); + } + + public Object get(String id, Scriptable start) { + return getPkgProperty(id, start, true); + } + + public Object get(int index, Scriptable start) { + return NOT_FOUND; + } + + // set up a name which is known to be a package so we don't + // need to look for a class by that name + void forcePackage(String name, Scriptable scope) + { + NativeJavaPackage pkg; + int end = name.indexOf('.'); + if (end == -1) { + end = name.length(); + } + + String id = name.substring(0, end); + Object cached = super.get(id, this); + if (cached != null && cached instanceof NativeJavaPackage) { + pkg = (NativeJavaPackage) cached; + } else { + String newPackage = packageName.length() == 0 + ? id + : packageName + "." + id; + pkg = new NativeJavaPackage(true, newPackage, classLoader); + ScriptRuntime.setObjectProtoAndParent(pkg, scope); + super.put(id, this, pkg); + } + if (end < name.length()) { + pkg.forcePackage(name.substring(end+1), scope); + } + } + + synchronized Object getPkgProperty(String name, Scriptable start, + boolean createPkg) + { + Object cached = super.get(name, start); + if (cached != NOT_FOUND) + return cached; + + String className = (packageName.length() == 0) + ? name : packageName + '.' + name; + Context cx = Context.getContext(); + ClassShutter shutter = cx.getClassShutter(); + Scriptable newValue = null; + if (shutter == null || shutter.visibleToScripts(className)) { + Class cl = null; + if (classLoader != null) { + cl = Kit.classOrNull(classLoader, className); + } else { + cl = Kit.classOrNull(className); + } + if (cl != null) { + newValue = new NativeJavaClass(getTopLevelScope(this), cl); + newValue.setPrototype(getPrototype()); + } + } + if (newValue == null && createPkg) { + NativeJavaPackage pkg; + pkg = new NativeJavaPackage(true, className, classLoader); + ScriptRuntime.setObjectProtoAndParent(pkg, getParentScope()); + newValue = pkg; + } + if (newValue != null) { + // Make it available for fast lookup and sharing of + // lazily-reflected constructors and static members. + super.put(name, start, newValue); + } + return newValue; + } + + public Object getDefaultValue(Class ignored) { + return toString(); + } + + public String toString() { + return "[JavaPackage " + packageName + "]"; + } + + public boolean equals(Object obj) { + if(obj instanceof NativeJavaPackage) { + NativeJavaPackage njp = (NativeJavaPackage)obj; + return packageName.equals(njp.packageName) && classLoader == njp.classLoader; + } + return false; + } + + public int hashCode() { + return packageName.hashCode() ^ (classLoader == null ? 0 : classLoader.hashCode()); + } + + private String packageName; + private ClassLoader classLoader; +}
\ No newline at end of file diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaTopPackage.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaTopPackage.java new file mode 100644 index 0000000..b5c9b49 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeJavaTopPackage.java @@ -0,0 +1,187 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Frank Mitchell + * Mike Shaver + * + * 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; + +/** + * This class reflects Java packages into the JavaScript environment. We + * lazily reflect classes and subpackages, and use a caching/sharing + * system to ensure that members reflected into one JavaPackage appear + * in all other references to the same package (as with Packages.java.lang + * and java.lang). + * + * @author Mike Shaver + * @see NativeJavaArray + * @see NativeJavaObject + * @see NativeJavaClass + */ + +public class NativeJavaTopPackage + extends NativeJavaPackage implements Function, IdFunctionCall +{ + static final long serialVersionUID = -1455787259477709999L; + + // we know these are packages so we can skip the class check + // note that this is ok even if the package isn't present. + private static final String commonPackages = "" + +"java.lang;" + +"java.lang.reflect;" + +"java.io;" + +"java.math;" + +"java.net;" + +"java.util;" + +"java.util.zip;" + +"java.text;" + +"java.text.resources;" + +"java.applet;" + +"javax.swing;" + ; + + NativeJavaTopPackage(ClassLoader loader) + { + super(true, "", loader); + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return construct(cx, scope, args); + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + ClassLoader loader = null; + if (args.length != 0) { + Object arg = args[0]; + if (arg instanceof Wrapper) { + arg = ((Wrapper)arg).unwrap(); + } + if (arg instanceof ClassLoader) { + loader = (ClassLoader)arg; + } + } + if (loader == null) { + Context.reportRuntimeError0("msg.not.classloader"); + return null; + } + return new NativeJavaPackage(true, "", loader); + } + + public static void init(Context cx, Scriptable scope, boolean sealed) + { + ClassLoader loader = cx.getApplicationClassLoader(); + final NativeJavaTopPackage top = new NativeJavaTopPackage(loader); + top.setPrototype(getObjectPrototype(scope)); + top.setParentScope(scope); + + String[] names = Kit.semicolonSplit(commonPackages); + for (int i = 0; i != names.length; ++i) { + top.forcePackage(names[i], scope); + } + + // getClass implementation + IdFunctionObject getClass = new IdFunctionObject(top, FTAG, Id_getClass, + "getClass", 1, scope); + + // We want to get a real alias, and not a distinct JavaPackage + // with the same packageName, so that we share classes and top + // that are underneath. + String[] topNames = { "java", "javax", "org", "com", "edu", "net" }; + NativeJavaPackage[] topPackages = new NativeJavaPackage[topNames.length]; + for (int i=0; i < topNames.length; i++) { + topPackages[i] = (NativeJavaPackage)top.get(topNames[i], top); + } + + // It's safe to downcast here since initStandardObjects takes + // a ScriptableObject. + ScriptableObject global = (ScriptableObject) scope; + + if (sealed) { + getClass.sealObject(); + } + getClass.exportAsScopeProperty(); + global.defineProperty("Packages", top, ScriptableObject.DONTENUM); + for (int i=0; i < topNames.length; i++) { + global.defineProperty(topNames[i], topPackages[i], + ScriptableObject.DONTENUM); + } + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (f.hasTag(FTAG)) { + if (f.methodId() == Id_getClass) { + return js_getClass(cx, scope, args); + } + } + throw f.unknown(); + } + + private Scriptable js_getClass(Context cx, Scriptable scope, Object[] args) + { + if (args.length > 0 && args[0] instanceof Wrapper) { + Scriptable result = this; + Class cl = ((Wrapper) args[0]).unwrap().getClass(); + // Evaluate the class name by getting successive properties of + // the string to find the appropriate NativeJavaClass object + String name = cl.getName(); + int offset = 0; + for (;;) { + int index = name.indexOf('.', offset); + String propName = index == -1 + ? name.substring(offset) + : name.substring(offset, index); + Object prop = result.get(propName, result); + if (!(prop instanceof Scriptable)) + break; // fall through to error + result = (Scriptable) prop; + if (index == -1) + return result; + offset = index+1; + } + } + throw Context.reportRuntimeError0("msg.not.java.obj"); + } + + private static final Object FTAG = new Object(); + private static final int Id_getClass = 1; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeMath.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeMath.java new file mode 100644 index 0000000..36b66b4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeMath.java @@ -0,0 +1,399 @@ +/* -*- Mode: java; tab-width: 4; indent-tabs-mode: 1; 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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; + +/** + * This class implements the Math native object. + * See ECMA 15.8. + * @author Norris Boyd + */ + +final class NativeMath extends IdScriptableObject +{ + static final long serialVersionUID = -8838847185801131569L; + + private static final Object MATH_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeMath obj = new NativeMath(); + obj.activatePrototypeMap(MAX_ID); + obj.setPrototype(getObjectPrototype(scope)); + obj.setParentScope(scope); + if (sealed) { obj.sealObject(); } + ScriptableObject.defineProperty(scope, "Math", obj, + ScriptableObject.DONTENUM); + } + + private NativeMath() + { + } + + public String getClassName() { return "Math"; } + + protected void initPrototypeId(int id) + { + if (id <= LAST_METHOD_ID) { + String name; + int arity; + switch (id) { + case Id_toSource: arity = 0; name = "toSource"; break; + case Id_abs: arity = 1; name = "abs"; break; + case Id_acos: arity = 1; name = "acos"; break; + case Id_asin: arity = 1; name = "asin"; break; + case Id_atan: arity = 1; name = "atan"; break; + case Id_atan2: arity = 2; name = "atan2"; break; + case Id_ceil: arity = 1; name = "ceil"; break; + case Id_cos: arity = 1; name = "cos"; break; + case Id_exp: arity = 1; name = "exp"; break; + case Id_floor: arity = 1; name = "floor"; break; + case Id_log: arity = 1; name = "log"; break; + case Id_max: arity = 2; name = "max"; break; + case Id_min: arity = 2; name = "min"; break; + case Id_pow: arity = 2; name = "pow"; break; + case Id_random: arity = 0; name = "random"; break; + case Id_round: arity = 1; name = "round"; break; + case Id_sin: arity = 1; name = "sin"; break; + case Id_sqrt: arity = 1; name = "sqrt"; break; + case Id_tan: arity = 1; name = "tan"; break; + default: throw new IllegalStateException(String.valueOf(id)); + } + initPrototypeMethod(MATH_TAG, id, name, arity); + } else { + String name; + double x; + switch (id) { + case Id_E: x = Math.E; name = "E"; break; + case Id_PI: x = Math.PI; name = "PI"; break; + case Id_LN10: x = 2.302585092994046; name = "LN10"; break; + case Id_LN2: x = 0.6931471805599453; name = "LN2"; break; + case Id_LOG2E: x = 1.4426950408889634; name = "LOG2E"; break; + case Id_LOG10E: x = 0.4342944819032518; name = "LOG10E"; break; + case Id_SQRT1_2: x = 0.7071067811865476; name = "SQRT1_2"; break; + case Id_SQRT2: x = 1.4142135623730951; name = "SQRT2"; break; + default: throw new IllegalStateException(String.valueOf(id)); + } + initPrototypeValue(id, name, ScriptRuntime.wrapNumber(x), + DONTENUM | READONLY | PERMANENT); + } + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(MATH_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + double x; + int methodId = f.methodId(); + switch (methodId) { + case Id_toSource: + return "Math"; + + case Id_abs: + x = ScriptRuntime.toNumber(args, 0); + // abs(-0.0) should be 0.0, but -0.0 < 0.0 == false + x = (x == 0.0) ? 0.0 : (x < 0.0) ? -x : x; + break; + + case Id_acos: + case Id_asin: + x = ScriptRuntime.toNumber(args, 0); + if (x == x && -1.0 <= x && x <= 1.0) { + x = (methodId == Id_acos) ? Math.acos(x) : Math.asin(x); + } else { + x = Double.NaN; + } + break; + + case Id_atan: + x = ScriptRuntime.toNumber(args, 0); + x = Math.atan(x); + break; + + case Id_atan2: + x = ScriptRuntime.toNumber(args, 0); + x = Math.atan2(x, ScriptRuntime.toNumber(args, 1)); + break; + + case Id_ceil: + x = ScriptRuntime.toNumber(args, 0); + x = Math.ceil(x); + break; + + case Id_cos: + x = ScriptRuntime.toNumber(args, 0); + x = (x == Double.POSITIVE_INFINITY + || x == Double.NEGATIVE_INFINITY) + ? Double.NaN : Math.cos(x); + break; + + case Id_exp: + x = ScriptRuntime.toNumber(args, 0); + x = (x == Double.POSITIVE_INFINITY) ? x + : (x == Double.NEGATIVE_INFINITY) ? 0.0 + : Math.exp(x); + break; + + case Id_floor: + x = ScriptRuntime.toNumber(args, 0); + x = Math.floor(x); + break; + + case Id_log: + x = ScriptRuntime.toNumber(args, 0); + // Java's log(<0) = -Infinity; we need NaN + x = (x < 0) ? Double.NaN : Math.log(x); + break; + + case Id_max: + case Id_min: + x = (methodId == Id_max) + ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY; + for (int i = 0; i != args.length; ++i) { + double d = ScriptRuntime.toNumber(args[i]); + if (d != d) { + x = d; // NaN + break; + } + if (methodId == Id_max) { + // if (x < d) x = d; does not work due to -0.0 >= +0.0 + x = Math.max(x, d); + } else { + x = Math.min(x, d); + } + } + break; + + case Id_pow: + x = ScriptRuntime.toNumber(args, 0); + x = js_pow(x, ScriptRuntime.toNumber(args, 1)); + break; + + case Id_random: + x = Math.random(); + break; + + case Id_round: + x = ScriptRuntime.toNumber(args, 0); + if (x == x && x != Double.POSITIVE_INFINITY + && x != Double.NEGATIVE_INFINITY) + { + // Round only finite x + long l = Math.round(x); + if (l != 0) { + x = l; + } else { + // We must propagate the sign of d into the result + if (x < 0.0) { + x = ScriptRuntime.negativeZero; + } else if (x != 0.0) { + x = 0.0; + } + } + } + break; + + case Id_sin: + x = ScriptRuntime.toNumber(args, 0); + x = (x == Double.POSITIVE_INFINITY + || x == Double.NEGATIVE_INFINITY) + ? Double.NaN : Math.sin(x); + break; + + case Id_sqrt: + x = ScriptRuntime.toNumber(args, 0); + x = Math.sqrt(x); + break; + + case Id_tan: + x = ScriptRuntime.toNumber(args, 0); + x = Math.tan(x); + break; + + default: throw new IllegalStateException(String.valueOf(methodId)); + } + return ScriptRuntime.wrapNumber(x); + } + + // See Ecma 15.8.2.13 + private double js_pow(double x, double y) { + double result; + if (y != y) { + // y is NaN, result is always NaN + result = y; + } else if (y == 0) { + // Java's pow(NaN, 0) = NaN; we need 1 + result = 1.0; + } else if (x == 0) { + // Many dirrerences from Java's Math.pow + if (1 / x > 0) { + result = (y > 0) ? 0 : Double.POSITIVE_INFINITY; + } else { + // x is -0, need to check if y is an odd integer + long y_long = (long)y; + if (y_long == y && (y_long & 0x1) != 0) { + result = (y > 0) ? -0.0 : Double.NEGATIVE_INFINITY; + } else { + result = (y > 0) ? 0.0 : Double.POSITIVE_INFINITY; + } + } + } else { + result = Math.pow(x, y); + if (result != result) { + // Check for broken Java implementations that gives NaN + // when they should return something else + if (y == Double.POSITIVE_INFINITY) { + if (x < -1.0 || 1.0 < x) { + result = Double.POSITIVE_INFINITY; + } else if (-1.0 < x && x < 1.0) { + result = 0; + } + } else if (y == Double.NEGATIVE_INFINITY) { + if (x < -1.0 || 1.0 < x) { + result = 0; + } else if (-1.0 < x && x < 1.0) { + result = Double.POSITIVE_INFINITY; + } + } else if (x == Double.POSITIVE_INFINITY) { + result = (y > 0) ? Double.POSITIVE_INFINITY : 0.0; + } else if (x == Double.NEGATIVE_INFINITY) { + long y_long = (long)y; + if (y_long == y && (y_long & 0x1) != 0) { + // y is odd integer + result = (y > 0) ? Double.NEGATIVE_INFINITY : -0.0; + } else { + result = (y > 0) ? Double.POSITIVE_INFINITY : 0.0; + } + } + } + } + return result; + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2004-03-17 13:51:32 CET + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 1: if (s.charAt(0)=='E') {id=Id_E; break L0;} break L; + case 2: if (s.charAt(0)=='P' && s.charAt(1)=='I') {id=Id_PI; break L0;} break L; + case 3: switch (s.charAt(0)) { + case 'L': if (s.charAt(2)=='2' && s.charAt(1)=='N') {id=Id_LN2; break L0;} break L; + case 'a': if (s.charAt(2)=='s' && s.charAt(1)=='b') {id=Id_abs; break L0;} break L; + case 'c': if (s.charAt(2)=='s' && s.charAt(1)=='o') {id=Id_cos; break L0;} break L; + case 'e': if (s.charAt(2)=='p' && s.charAt(1)=='x') {id=Id_exp; break L0;} break L; + case 'l': if (s.charAt(2)=='g' && s.charAt(1)=='o') {id=Id_log; break L0;} break L; + case 'm': c=s.charAt(2); + if (c=='n') { if (s.charAt(1)=='i') {id=Id_min; break L0;} } + else if (c=='x') { if (s.charAt(1)=='a') {id=Id_max; break L0;} } + break L; + case 'p': if (s.charAt(2)=='w' && s.charAt(1)=='o') {id=Id_pow; break L0;} break L; + case 's': if (s.charAt(2)=='n' && s.charAt(1)=='i') {id=Id_sin; break L0;} break L; + case 't': if (s.charAt(2)=='n' && s.charAt(1)=='a') {id=Id_tan; break L0;} break L; + } break L; + case 4: switch (s.charAt(1)) { + case 'N': X="LN10";id=Id_LN10; break L; + case 'c': X="acos";id=Id_acos; break L; + case 'e': X="ceil";id=Id_ceil; break L; + case 'q': X="sqrt";id=Id_sqrt; break L; + case 's': X="asin";id=Id_asin; break L; + case 't': X="atan";id=Id_atan; break L; + } break L; + case 5: switch (s.charAt(0)) { + case 'L': X="LOG2E";id=Id_LOG2E; break L; + case 'S': X="SQRT2";id=Id_SQRT2; break L; + case 'a': X="atan2";id=Id_atan2; break L; + case 'f': X="floor";id=Id_floor; break L; + case 'r': X="round";id=Id_round; break L; + } break L; + case 6: c=s.charAt(0); + if (c=='L') { X="LOG10E";id=Id_LOG10E; } + else if (c=='r') { X="random";id=Id_random; } + break L; + case 7: X="SQRT1_2";id=Id_SQRT1_2; break L; + case 8: X="toSource";id=Id_toSource; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_toSource = 1, + Id_abs = 2, + Id_acos = 3, + Id_asin = 4, + Id_atan = 5, + Id_atan2 = 6, + Id_ceil = 7, + Id_cos = 8, + Id_exp = 9, + Id_floor = 10, + Id_log = 11, + Id_max = 12, + Id_min = 13, + Id_pow = 14, + Id_random = 15, + Id_round = 16, + Id_sin = 17, + Id_sqrt = 18, + Id_tan = 19, + + LAST_METHOD_ID = 19; + + private static final int + Id_E = LAST_METHOD_ID + 1, + Id_PI = LAST_METHOD_ID + 2, + Id_LN10 = LAST_METHOD_ID + 3, + Id_LN2 = LAST_METHOD_ID + 4, + Id_LOG2E = LAST_METHOD_ID + 5, + Id_LOG10E = LAST_METHOD_ID + 6, + Id_SQRT1_2 = LAST_METHOD_ID + 7, + Id_SQRT2 = LAST_METHOD_ID + 8, + + MAX_ID = LAST_METHOD_ID + 8; + +// #/string_id_map# +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeNumber.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeNumber.java new file mode 100644 index 0000000..8fc9fb0 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeNumber.java @@ -0,0 +1,244 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Mike McCabe + * + * 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; + +/** + * This class implements the Number native object. + * + * See ECMA 15.7. + * + * @author Norris Boyd + */ +final class NativeNumber extends IdScriptableObject +{ + static final long serialVersionUID = 3504516769741512101L; + + private static final Object NUMBER_TAG = new Object(); + + private static final int MAX_PRECISION = 100; + + static void init(Scriptable scope, boolean sealed) + { + NativeNumber obj = new NativeNumber(0.0); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + private NativeNumber(double number) + { + doubleValue = number; + } + + public String getClassName() + { + return "Number"; + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + final int attr = ScriptableObject.DONTENUM | + ScriptableObject.PERMANENT | + ScriptableObject.READONLY; + + ctor.defineProperty("NaN", ScriptRuntime.NaNobj, attr); + ctor.defineProperty("POSITIVE_INFINITY", + ScriptRuntime.wrapNumber(Double.POSITIVE_INFINITY), + attr); + ctor.defineProperty("NEGATIVE_INFINITY", + ScriptRuntime.wrapNumber(Double.NEGATIVE_INFINITY), + attr); + ctor.defineProperty("MAX_VALUE", + ScriptRuntime.wrapNumber(Double.MAX_VALUE), + attr); + ctor.defineProperty("MIN_VALUE", + ScriptRuntime.wrapNumber(Double.MIN_VALUE), + attr); + + super.fillConstructorProperties(ctor); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=1; s="toString"; break; + case Id_toLocaleString: arity=1; s="toLocaleString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_valueOf: arity=0; s="valueOf"; break; + case Id_toFixed: arity=1; s="toFixed"; break; + case Id_toExponential: arity=1; s="toExponential"; break; + case Id_toPrecision: arity=1; s="toPrecision"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(NUMBER_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(NUMBER_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + if (id == Id_constructor) { + double val = (args.length >= 1) + ? ScriptRuntime.toNumber(args[0]) : 0.0; + if (thisObj == null) { + // new Number(val) creates a new Number object. + return new NativeNumber(val); + } + // Number(val) converts val to a number value. + return ScriptRuntime.wrapNumber(val); + } + + // The rest of Number.prototype methods require thisObj to be Number + + if (!(thisObj instanceof NativeNumber)) + throw incompatibleCallError(f); + double value = ((NativeNumber)thisObj).doubleValue; + + switch (id) { + + case Id_toString: + case Id_toLocaleString: + { + // toLocaleString is just an alias for toString for now + int base = (args.length == 0) + ? 10 : ScriptRuntime.toInt32(args[0]); + return ScriptRuntime.numberToString(value, base); + } + + case Id_toSource: + return "(new Number("+ScriptRuntime.toString(value)+"))"; + + case Id_valueOf: + return ScriptRuntime.wrapNumber(value); + + case Id_toFixed: + return num_to(value, args, DToA.DTOSTR_FIXED, + DToA.DTOSTR_FIXED, -20, 0); + + case Id_toExponential: + return num_to(value, args, DToA.DTOSTR_STANDARD_EXPONENTIAL, + DToA.DTOSTR_EXPONENTIAL, 0, 1); + + case Id_toPrecision: + return num_to(value, args, DToA.DTOSTR_STANDARD, + DToA.DTOSTR_PRECISION, 1, 0); + + default: throw new IllegalArgumentException(String.valueOf(id)); + } + } + + public String toString() { + return ScriptRuntime.numberToString(doubleValue, 10); + } + + private static String num_to(double val, + Object[] args, + int zeroArgMode, int oneArgMode, + int precisionMin, int precisionOffset) + { + int precision; + if (args.length == 0) { + precision = 0; + oneArgMode = zeroArgMode; + } else { + /* We allow a larger range of precision than + ECMA requires; this is permitted by ECMA. */ + precision = ScriptRuntime.toInt32(args[0]); + if (precision < precisionMin || precision > MAX_PRECISION) { + String msg = ScriptRuntime.getMessage1( + "msg.bad.precision", ScriptRuntime.toString(args[0])); + throw ScriptRuntime.constructError("RangeError", msg); + } + } + StringBuffer sb = new StringBuffer(); + DToA.JS_dtostr(sb, oneArgMode, precision + precisionOffset, val); + return sb.toString(); + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:50 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 7: c=s.charAt(0); + if (c=='t') { X="toFixed";id=Id_toFixed; } + else if (c=='v') { X="valueOf";id=Id_valueOf; } + break L; + case 8: c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + case 11: c=s.charAt(0); + if (c=='c') { X="constructor";id=Id_constructor; } + else if (c=='t') { X="toPrecision";id=Id_toPrecision; } + break L; + case 13: X="toExponential";id=Id_toExponential; break L; + case 14: X="toLocaleString";id=Id_toLocaleString; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toLocaleString = 3, + Id_toSource = 4, + Id_valueOf = 5, + Id_toFixed = 6, + Id_toExponential = 7, + Id_toPrecision = 8, + MAX_PROTOTYPE_ID = 8; + +// #/string_id_map# + + private double doubleValue; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeObject.java new file mode 100644 index 0000000..19aff63 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeObject.java @@ -0,0 +1,316 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Bob Jervis + * Mike McCabe + * + * 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; + +/** + * This class implements the Object native object. + * See ECMA 15.2. + * @author Norris Boyd + */ +public class NativeObject extends IdScriptableObject +{ + static final long serialVersionUID = -6345305608474346996L; + + private static final Object OBJECT_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeObject obj = new NativeObject(); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + public String getClassName() + { + return "Object"; + } + + public String toString() + { + return ScriptRuntime.defaultObjectToString(this); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toLocaleString: arity=0; s="toLocaleString"; break; + case Id_valueOf: arity=0; s="valueOf"; break; + case Id_hasOwnProperty: arity=1; s="hasOwnProperty"; break; + case Id_propertyIsEnumerable: + arity=1; s="propertyIsEnumerable"; break; + case Id_isPrototypeOf: arity=1; s="isPrototypeOf"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id___defineGetter__: + arity=2; s="__defineGetter__"; break; + case Id___defineSetter__: + arity=2; s="__defineSetter__"; break; + case Id___lookupGetter__: + arity=1; s="__lookupGetter__"; break; + case Id___lookupSetter__: + arity=1; s="__lookupSetter__"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(OBJECT_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(OBJECT_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: { + if (thisObj != null) { + // BaseFunction.construct will set up parent, proto + return f.construct(cx, scope, args); + } + if (args.length == 0 || args[0] == null + || args[0] == Undefined.instance) + { + return new NativeObject(); + } + return ScriptRuntime.toObject(cx, scope, args[0]); + } + + case Id_toLocaleString: // For now just alias toString + case Id_toString: { + if (cx.hasFeature(Context.FEATURE_TO_STRING_AS_SOURCE)) { + String s = ScriptRuntime.defaultObjectToSource(cx, scope, + thisObj, args); + int L = s.length(); + if (L != 0 && s.charAt(0) == '(' && s.charAt(L - 1) == ')') { + // Strip () that surrounds toSource + s = s.substring(1, L - 1); + } + return s; + } + return ScriptRuntime.defaultObjectToString(thisObj); + } + + case Id_valueOf: + return thisObj; + + case Id_hasOwnProperty: { + boolean result; + if (args.length == 0) { + result = false; + } else { + String s = ScriptRuntime.toStringIdOrIndex(cx, args[0]); + if (s == null) { + int index = ScriptRuntime.lastIndexResult(cx); + result = thisObj.has(index, thisObj); + } else { + result = thisObj.has(s, thisObj); + } + } + return ScriptRuntime.wrapBoolean(result); + } + + case Id_propertyIsEnumerable: { + boolean result; + if (args.length == 0) { + result = false; + } else { + String s = ScriptRuntime.toStringIdOrIndex(cx, args[0]); + if (s == null) { + int index = ScriptRuntime.lastIndexResult(cx); + result = thisObj.has(index, thisObj); + if (result && thisObj instanceof ScriptableObject) { + ScriptableObject so = (ScriptableObject)thisObj; + int attrs = so.getAttributes(index); + result = ((attrs & ScriptableObject.DONTENUM) == 0); + } + } else { + result = thisObj.has(s, thisObj); + if (result && thisObj instanceof ScriptableObject) { + ScriptableObject so = (ScriptableObject)thisObj; + int attrs = so.getAttributes(s); + result = ((attrs & ScriptableObject.DONTENUM) == 0); + } + } + } + return ScriptRuntime.wrapBoolean(result); + } + + case Id_isPrototypeOf: { + boolean result = false; + if (args.length != 0 && args[0] instanceof Scriptable) { + Scriptable v = (Scriptable) args[0]; + do { + v = v.getPrototype(); + if (v == thisObj) { + result = true; + break; + } + } while (v != null); + } + return ScriptRuntime.wrapBoolean(result); + } + + case Id_toSource: + return ScriptRuntime.defaultObjectToSource(cx, scope, thisObj, + args); + case Id___defineGetter__: + case Id___defineSetter__: + { + if (args.length < 2 || !(args[1] instanceof Callable)) { + Object badArg = (args.length >= 2 ? args[1] + : Undefined.instance); + throw ScriptRuntime.notFunctionError(badArg); + } + if (!(thisObj instanceof ScriptableObject)) { + throw Context.reportRuntimeError2( + "msg.extend.scriptable", + thisObj.getClass().getName(), + String.valueOf(args[0])); + } + ScriptableObject so = (ScriptableObject)thisObj; + String name = ScriptRuntime.toStringIdOrIndex(cx, args[0]); + int index = (name != null ? 0 + : ScriptRuntime.lastIndexResult(cx)); + Callable getterOrSetter = (Callable)args[1]; + boolean isSetter = (id == Id___defineSetter__); + so.setGetterOrSetter(name, index, getterOrSetter, isSetter); + if (so instanceof NativeArray) + ((NativeArray)so).setDenseOnly(false); + } + return Undefined.instance; + + case Id___lookupGetter__: + case Id___lookupSetter__: + { + if (args.length < 1 || + !(thisObj instanceof ScriptableObject)) + return Undefined.instance; + + ScriptableObject so = (ScriptableObject)thisObj; + String name = ScriptRuntime.toStringIdOrIndex(cx, args[0]); + int index = (name != null ? 0 + : ScriptRuntime.lastIndexResult(cx)); + boolean isSetter = (id == Id___lookupSetter__); + Object gs; + for (;;) { + gs = so.getGetterOrSetter(name, index, isSetter); + if (gs != null) + break; + // If there is no getter or setter for the object itself, + // how about the prototype? + Scriptable v = so.getPrototype(); + if (v == null) + break; + if (v instanceof ScriptableObject) + so = (ScriptableObject)v; + else + break; + } + if (gs != null) + return gs; + } + return Undefined.instance; + + default: + throw new IllegalArgumentException(String.valueOf(id)); + } + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:15:55 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 7: X="valueOf";id=Id_valueOf; break L; + case 8: c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + case 11: X="constructor";id=Id_constructor; break L; + case 13: X="isPrototypeOf";id=Id_isPrototypeOf; break L; + case 14: c=s.charAt(0); + if (c=='h') { X="hasOwnProperty";id=Id_hasOwnProperty; } + else if (c=='t') { X="toLocaleString";id=Id_toLocaleString; } + break L; + case 16: c=s.charAt(2); + if (c=='d') { + c=s.charAt(8); + if (c=='G') { X="__defineGetter__";id=Id___defineGetter__; } + else if (c=='S') { X="__defineSetter__";id=Id___defineSetter__; } + } + else if (c=='l') { + c=s.charAt(8); + if (c=='G') { X="__lookupGetter__";id=Id___lookupGetter__; } + else if (c=='S') { X="__lookupSetter__";id=Id___lookupSetter__; } + } + break L; + case 20: X="propertyIsEnumerable";id=Id_propertyIsEnumerable; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_toLocaleString = 3, + Id_valueOf = 4, + Id_hasOwnProperty = 5, + Id_propertyIsEnumerable = 6, + Id_isPrototypeOf = 7, + Id_toSource = 8, + Id___defineGetter__ = 9, + Id___defineSetter__ = 10, + Id___lookupGetter__ = 11, + Id___lookupSetter__ = 12, + MAX_PROTOTYPE_ID = 12; + +// #/string_id_map# +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeScript.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeScript.java new file mode 100644 index 0000000..7b5191e --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeScript.java @@ -0,0 +1,221 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * Mike McCabe + * + * 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; + +/** + * The JavaScript Script object. + * + * Note that the C version of the engine uses XDR as the format used + * by freeze and thaw. Since this depends on the internal format of + * structures in the C runtime, we cannot duplicate it. + * + * Since we cannot replace 'this' as a result of the compile method, + * will forward requests to execute to the nonnull 'script' field. + * + * @since 1.3 + * @author Norris Boyd + */ + +class NativeScript extends BaseFunction +{ + static final long serialVersionUID = -6795101161980121700L; + + private static final Object SCRIPT_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeScript obj = new NativeScript(null); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + private NativeScript(Script script) + { + this.script = script; + } + + /** + * Returns the name of this JavaScript class, "Script". + */ + public String getClassName() + { + return "Script"; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + if (script != null) { + return script.exec(cx, scope); + } + return Undefined.instance; + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + throw Context.reportRuntimeError0("msg.script.is.not.constructor"); + } + + public int getLength() + { + return 0; + } + + public int getArity() + { + return 0; + } + + String decompile(int indent, int flags) + { + if (script instanceof NativeFunction) { + return ((NativeFunction)script).decompile(indent, flags); + } + return super.decompile(indent, flags); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_exec: arity=0; s="exec"; break; + case Id_compile: arity=1; s="compile"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(SCRIPT_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(SCRIPT_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: { + String source = (args.length == 0) + ? "" + : ScriptRuntime.toString(args[0]); + Script script = compile(cx, source); + NativeScript nscript = new NativeScript(script); + ScriptRuntime.setObjectProtoAndParent(nscript, scope); + return nscript; + } + + case Id_toString: { + NativeScript real = realThis(thisObj, f); + Script realScript = real.script; + if (realScript == null) { return ""; } + return cx.decompileScript(realScript, 0); + } + + case Id_exec: { + throw Context.reportRuntimeError1( + "msg.cant.call.indirect", "exec"); + } + + case Id_compile: { + NativeScript real = realThis(thisObj, f); + String source = ScriptRuntime.toString(args, 0); + real.script = compile(cx, source); + return real; + } + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private static NativeScript realThis(Scriptable thisObj, IdFunctionObject f) + { + if (!(thisObj instanceof NativeScript)) + throw incompatibleCallError(f); + return (NativeScript)thisObj; + } + + private static Script compile(Context cx, String source) + { + int[] linep = { 0 }; + String filename = Context.getSourcePositionFromStack(linep); + if (filename == null) { + filename = "<Script object>"; + linep[0] = 1; + } + ErrorReporter reporter; + reporter = DefaultErrorReporter.forEval(cx.getErrorReporter()); + return cx.compileString(source, null, reporter, filename, + linep[0], null); + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:16:01 EDT + L0: { id = 0; String X = null; + L: switch (s.length()) { + case 4: X="exec";id=Id_exec; break L; + case 7: X="compile";id=Id_compile; break L; + case 8: X="toString";id=Id_toString; break L; + case 11: X="constructor";id=Id_constructor; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + Id_toString = 2, + Id_compile = 3, + Id_exec = 4, + MAX_PROTOTYPE_ID = 4; + +// #/string_id_map# + + private Script script; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeString.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeString.java new file mode 100644 index 0000000..972415d --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeString.java @@ -0,0 +1,983 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Tom Beauvais + * Norris Boyd + * Mike McCabe + * + * 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.text.Collator; + +/** + * This class implements the String native object. + * + * See ECMA 15.5. + * + * String methods for dealing with regular expressions are + * ported directly from C. Latest port is from version 1.40.12.19 + * in the JSFUN13_BRANCH. + * + * @author Mike McCabe + * @author Norris Boyd + */ +final class NativeString extends IdScriptableObject +{ + static final long serialVersionUID = 920268368584188687L; + + private static final Object STRING_TAG = new Object(); + + static void init(Scriptable scope, boolean sealed) + { + NativeString obj = new NativeString(""); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + private NativeString(String s) { + string = s; + } + + public String getClassName() { + return "String"; + } + + private static final int + Id_length = 1, + MAX_INSTANCE_ID = 1; + + protected int getMaxInstanceId() + { + return MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) + { + if (s.equals("length")) { + return instanceIdInfo(DONTENUM | READONLY | PERMANENT, Id_length); + } + return super.findInstanceIdInfo(s); + } + + protected String getInstanceIdName(int id) + { + if (id == Id_length) { return "length"; } + return super.getInstanceIdName(id); + } + + protected Object getInstanceIdValue(int id) + { + if (id == Id_length) { + return ScriptRuntime.wrapInt(string.length()); + } + return super.getInstanceIdValue(id); + } + + protected void fillConstructorProperties(IdFunctionObject ctor) + { + addIdFunctionProperty(ctor, STRING_TAG, ConstructorId_fromCharCode, + "fromCharCode", 1); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_charAt, "charAt", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_charCodeAt, "charCodeAt", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_indexOf, "indexOf", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_lastIndexOf, "lastIndexOf", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_split, "split", 3); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_substring, "substring", 3); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_toLowerCase, "toLowerCase", 1); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_toUpperCase, "toUpperCase", 1); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_substr, "substr", 3); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_concat, "concat", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_slice, "slice", 3); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_equalsIgnoreCase, "equalsIgnoreCase", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_match, "match", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_search, "search", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_replace, "replace", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_localeCompare, "localeCompare", 2); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_toLocaleLowerCase, "toLocaleLowerCase", 1); + addIdFunctionProperty(ctor, STRING_TAG, + ConstructorId_fromCharCode, "fromCharCode", 1); + super.fillConstructorProperties(ctor); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=1; s="constructor"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_valueOf: arity=0; s="valueOf"; break; + case Id_charAt: arity=1; s="charAt"; break; + case Id_charCodeAt: arity=1; s="charCodeAt"; break; + case Id_indexOf: arity=1; s="indexOf"; break; + case Id_lastIndexOf: arity=1; s="lastIndexOf"; break; + case Id_split: arity=2; s="split"; break; + case Id_substring: arity=2; s="substring"; break; + case Id_toLowerCase: arity=0; s="toLowerCase"; break; + case Id_toUpperCase: arity=0; s="toUpperCase"; break; + case Id_substr: arity=2; s="substr"; break; + case Id_concat: arity=1; s="concat"; break; + case Id_slice: arity=2; s="slice"; break; + case Id_bold: arity=0; s="bold"; break; + case Id_italics: arity=0; s="italics"; break; + case Id_fixed: arity=0; s="fixed"; break; + case Id_strike: arity=0; s="strike"; break; + case Id_small: arity=0; s="small"; break; + case Id_big: arity=0; s="big"; break; + case Id_blink: arity=0; s="blink"; break; + case Id_sup: arity=0; s="sup"; break; + case Id_sub: arity=0; s="sub"; break; + case Id_fontsize: arity=0; s="fontsize"; break; + case Id_fontcolor: arity=0; s="fontcolor"; break; + case Id_link: arity=0; s="link"; break; + case Id_anchor: arity=0; s="anchor"; break; + case Id_equals: arity=1; s="equals"; break; + case Id_equalsIgnoreCase: arity=1; s="equalsIgnoreCase"; break; + case Id_match: arity=1; s="match"; break; + case Id_search: arity=1; s="search"; break; + case Id_replace: arity=1; s="replace"; break; + case Id_localeCompare: arity=1; s="localeCompare"; break; + case Id_toLocaleLowerCase: arity=0; s="toLocaleLowerCase"; break; + case Id_toLocaleUpperCase: arity=0; s="toLocaleUpperCase"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(STRING_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(STRING_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + again: + for(;;) { + switch (id) { + case ConstructorId_charAt: + case ConstructorId_charCodeAt: + case ConstructorId_indexOf: + case ConstructorId_lastIndexOf: + case ConstructorId_split: + case ConstructorId_substring: + case ConstructorId_toLowerCase: + case ConstructorId_toUpperCase: + case ConstructorId_substr: + case ConstructorId_concat: + case ConstructorId_slice: + case ConstructorId_equalsIgnoreCase: + case ConstructorId_match: + case ConstructorId_search: + case ConstructorId_replace: + case ConstructorId_localeCompare: + case ConstructorId_toLocaleLowerCase: { + thisObj = ScriptRuntime.toObject(scope, + ScriptRuntime.toString(args[0])); + Object[] newArgs = new Object[args.length-1]; + for (int i=0; i < newArgs.length; i++) + newArgs[i] = args[i+1]; + args = newArgs; + id = -id; + continue again; + } + + case ConstructorId_fromCharCode: { + int N = args.length; + if (N < 1) + return ""; + StringBuffer sb = new StringBuffer(N); + for (int i = 0; i != N; ++i) { + sb.append(ScriptRuntime.toUint16(args[i])); + } + return sb.toString(); + } + + case Id_constructor: { + String s = (args.length >= 1) + ? ScriptRuntime.toString(args[0]) : ""; + if (thisObj == null) { + // new String(val) creates a new String object. + return new NativeString(s); + } + // String(val) converts val to a string value. + return s; + } + + case Id_toString: + case Id_valueOf: + // ECMA 15.5.4.2: 'the toString function is not generic. + return realThis(thisObj, f).string; + + case Id_toSource: { + String s = realThis(thisObj, f).string; + return "(new String(\""+ScriptRuntime.escapeString(s)+"\"))"; + } + + case Id_charAt: + case Id_charCodeAt: { + // See ECMA 15.5.4.[4,5] + String target = ScriptRuntime.toString(thisObj); + double pos = ScriptRuntime.toInteger(args, 0); + if (pos < 0 || pos >= target.length()) { + if (id == Id_charAt) return ""; + else return ScriptRuntime.NaNobj; + } + char c = target.charAt((int)pos); + if (id == Id_charAt) return String.valueOf(c); + else return ScriptRuntime.wrapInt(c); + } + + case Id_indexOf: + return ScriptRuntime.wrapInt(js_indexOf( + ScriptRuntime.toString(thisObj), args)); + + case Id_lastIndexOf: + return ScriptRuntime.wrapInt(js_lastIndexOf( + ScriptRuntime.toString(thisObj), args)); + + case Id_split: + return js_split(cx, scope, ScriptRuntime.toString(thisObj), + args); + + case Id_substring: + return js_substring(cx, ScriptRuntime.toString(thisObj), args); + + case Id_toLowerCase: + // See ECMA 15.5.4.11 + return ScriptRuntime.toString(thisObj).toLowerCase(); + + case Id_toUpperCase: + // See ECMA 15.5.4.12 + return ScriptRuntime.toString(thisObj).toUpperCase(); + + case Id_substr: + return js_substr(ScriptRuntime.toString(thisObj), args); + + case Id_concat: + return js_concat(ScriptRuntime.toString(thisObj), args); + + case Id_slice: + return js_slice(ScriptRuntime.toString(thisObj), args); + + case Id_bold: + return tagify(thisObj, "b", null, null); + + case Id_italics: + return tagify(thisObj, "i", null, null); + + case Id_fixed: + return tagify(thisObj, "tt", null, null); + + case Id_strike: + return tagify(thisObj, "strike", null, null); + + case Id_small: + return tagify(thisObj, "small", null, null); + + case Id_big: + return tagify(thisObj, "big", null, null); + + case Id_blink: + return tagify(thisObj, "blink", null, null); + + case Id_sup: + return tagify(thisObj, "sup", null, null); + + case Id_sub: + return tagify(thisObj, "sub", null, null); + + case Id_fontsize: + return tagify(thisObj, "font", "size", args); + + case Id_fontcolor: + return tagify(thisObj, "font", "color", args); + + case Id_link: + return tagify(thisObj, "a", "href", args); + + case Id_anchor: + return tagify(thisObj, "a", "name", args); + + case Id_equals: + case Id_equalsIgnoreCase: { + String s1 = ScriptRuntime.toString(thisObj); + String s2 = ScriptRuntime.toString(args, 0); + return ScriptRuntime.wrapBoolean( + (id == Id_equals) ? s1.equals(s2) + : s1.equalsIgnoreCase(s2)); + } + + case Id_match: + case Id_search: + case Id_replace: + { + int actionType; + if (id == Id_match) { + actionType = RegExpProxy.RA_MATCH; + } else if (id == Id_search) { + actionType = RegExpProxy.RA_SEARCH; + } else { + actionType = RegExpProxy.RA_REPLACE; + } + return ScriptRuntime.checkRegExpProxy(cx). + action(cx, scope, thisObj, args, actionType); + } + // ECMA-262 1 5.5.4.9 + case Id_localeCompare: + { + // For now, create and configure a collator instance. I can't + // actually imagine that this'd be slower than caching them + // a la ClassCache, so we aren't trying to outsmart ourselves + // with a caching mechanism for now. + Collator collator = Collator.getInstance(cx.getLocale()); + collator.setStrength(Collator.IDENTICAL); + collator.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + return ScriptRuntime.wrapNumber(collator.compare( + ScriptRuntime.toString(thisObj), + ScriptRuntime.toString(args, 0))); + } + case Id_toLocaleLowerCase: + { + return ScriptRuntime.toString(thisObj) + .toLowerCase(cx.getLocale()); + } + case Id_toLocaleUpperCase: + { + return ScriptRuntime.toString(thisObj) + .toUpperCase(cx.getLocale()); + } + } + throw new IllegalArgumentException(String.valueOf(id)); + } + } + + private static NativeString realThis(Scriptable thisObj, IdFunctionObject f) + { + if (!(thisObj instanceof NativeString)) + throw incompatibleCallError(f); + return (NativeString)thisObj; + } + + /* + * HTML composition aids. + */ + private static String tagify(Object thisObj, String tag, + String attribute, Object[] args) + { + String str = ScriptRuntime.toString(thisObj); + StringBuffer result = new StringBuffer(); + result.append('<'); + result.append(tag); + if (attribute != null) { + result.append(' '); + result.append(attribute); + result.append("=\""); + result.append(ScriptRuntime.toString(args, 0)); + result.append('"'); + } + result.append('>'); + result.append(str); + result.append("</"); + result.append(tag); + result.append('>'); + return result.toString(); + } + + public String toString() { + return string; + } + + /* Make array-style property lookup work for strings. + * XXX is this ECMA? A version check is probably needed. In js too. + */ + public Object get(int index, Scriptable start) { + if (0 <= index && index < string.length()) { + return string.substring(index, index + 1); + } + return super.get(index, start); + } + + public void put(int index, Scriptable start, Object value) { + if (0 <= index && index < string.length()) { + return; + } + super.put(index, start, value); + } + + /* + * + * See ECMA 15.5.4.6. Uses Java String.indexOf() + * OPT to add - BMH searching from jsstr.c. + */ + private static int js_indexOf(String target, Object[] args) { + String search = ScriptRuntime.toString(args, 0); + double begin = ScriptRuntime.toInteger(args, 1); + + if (begin > target.length()) { + return -1; + } else { + if (begin < 0) + begin = 0; + return target.indexOf(search, (int)begin); + } + } + + /* + * + * See ECMA 15.5.4.7 + * + */ + private static int js_lastIndexOf(String target, Object[] args) { + String search = ScriptRuntime.toString(args, 0); + double end = ScriptRuntime.toNumber(args, 1); + + if (end != end || end > target.length()) + end = target.length(); + else if (end < 0) + end = 0; + + return target.lastIndexOf(search, (int)end); + } + + /* + * Used by js_split to find the next split point in target, + * starting at offset ip and looking either for the given + * separator substring, or for the next re match. ip and + * matchlen must be reference variables (assumed to be arrays of + * length 1) so they can be updated in the leading whitespace or + * re case. + * + * Return -1 on end of string, >= 0 for a valid index of the next + * separator occurrence if found, or the string length if no + * separator is found. + */ + private static int find_split(Context cx, Scriptable scope, String target, + String separator, int version, + RegExpProxy reProxy, Scriptable re, + int[] ip, int[] matchlen, boolean[] matched, + String[][] parensp) + { + int i = ip[0]; + int length = target.length(); + + /* + * Perl4 special case for str.split(' '), only if the user has selected + * JavaScript1.2 explicitly. Split on whitespace, and skip leading w/s. + * Strange but true, apparently modeled after awk. + */ + if (version == Context.VERSION_1_2 && + re == null && separator.length() == 1 && separator.charAt(0) == ' ') + { + /* Skip leading whitespace if at front of str. */ + if (i == 0) { + while (i < length && Character.isWhitespace(target.charAt(i))) + i++; + ip[0] = i; + } + + /* Don't delimit whitespace at end of string. */ + if (i == length) + return -1; + + /* Skip over the non-whitespace chars. */ + while (i < length + && !Character.isWhitespace(target.charAt(i))) + i++; + + /* Now skip the next run of whitespace. */ + int j = i; + while (j < length && Character.isWhitespace(target.charAt(j))) + j++; + + /* Update matchlen to count delimiter chars. */ + matchlen[0] = j - i; + return i; + } + + /* + * Stop if past end of string. If at end of string, we will + * return target length, so that + * + * "ab,".split(',') => new Array("ab", "") + * + * and the resulting array converts back to the string "ab," + * for symmetry. NB: This differs from perl, which drops the + * trailing empty substring if the LIMIT argument is omitted. + */ + if (i > length) + return -1; + + /* + * Match a regular expression against the separator at or + * above index i. Return -1 at end of string instead of + * trying for a match, so we don't get stuck in a loop. + */ + if (re != null) { + return reProxy.find_split(cx, scope, target, separator, re, + ip, matchlen, matched, parensp); + } + + /* + * Deviate from ECMA by never splitting an empty string by any separator + * string into a non-empty array (an array of length 1 that contains the + * empty string). + */ + if (version != Context.VERSION_DEFAULT && version < Context.VERSION_1_3 + && length == 0) + return -1; + + /* + * Special case: if sep is the empty string, split str into + * one character substrings. Let our caller worry about + * whether to split once at end of string into an empty + * substring. + * + * For 1.2 compatibility, at the end of the string, we return the length as + * the result, and set the separator length to 1 -- this allows the caller + * to include an additional null string at the end of the substring list. + */ + if (separator.length() == 0) { + if (version == Context.VERSION_1_2) { + if (i == length) { + matchlen[0] = 1; + return i; + } + return i + 1; + } + return (i == length) ? -1 : i + 1; + } + + /* Punt to j.l.s.indexOf; return target length if separator is + * not found. + */ + if (ip[0] >= length) + return length; + + i = target.indexOf(separator, ip[0]); + + return (i != -1) ? i : length; + } + + /* + * See ECMA 15.5.4.8. Modified to match JS 1.2 - optionally takes + * a limit argument and accepts a regular expression as the split + * argument. + */ + private static Object js_split(Context cx, Scriptable scope, + String target, Object[] args) + { + // create an empty Array to return; + Scriptable top = getTopLevelScope(scope); + Scriptable result = ScriptRuntime.newObject(cx, top, "Array", null); + + // return an array consisting of the target if no separator given + // don't check against undefined, because we want + // 'fooundefinedbar'.split(void 0) to split to ['foo', 'bar'] + if (args.length < 1) { + result.put(0, result, target); + return result; + } + + // Use the second argument as the split limit, if given. + boolean limited = (args.length > 1) && (args[1] != Undefined.instance); + long limit = 0; // Initialize to avoid warning. + if (limited) { + /* Clamp limit between 0 and 1 + string length. */ + limit = ScriptRuntime.toUint32(args[1]); + if (limit > target.length()) + limit = 1 + target.length(); + } + + String separator = null; + int[] matchlen = new int[1]; + Scriptable re = null; + RegExpProxy reProxy = null; + if (args[0] instanceof Scriptable) { + reProxy = ScriptRuntime.getRegExpProxy(cx); + if (reProxy != null) { + Scriptable test = (Scriptable)args[0]; + if (reProxy.isRegExp(test)) { + re = test; + } + } + } + if (re == null) { + separator = ScriptRuntime.toString(args[0]); + matchlen[0] = separator.length(); + } + + // split target with separator or re + int[] ip = { 0 }; + int match; + int len = 0; + boolean[] matched = { false }; + String[][] parens = { null }; + int version = cx.getLanguageVersion(); + while ((match = find_split(cx, scope, target, separator, version, + reProxy, re, ip, matchlen, matched, parens)) + >= 0) + { + if ((limited && len >= limit) || (match > target.length())) + break; + + String substr; + if (target.length() == 0) + substr = target; + else + substr = target.substring(ip[0], match); + + result.put(len, result, substr); + len++; + /* + * Imitate perl's feature of including parenthesized substrings + * that matched part of the delimiter in the new array, after the + * split substring that was delimited. + */ + if (re != null && matched[0] == true) { + int size = parens[0].length; + for (int num = 0; num < size; num++) { + if (limited && len >= limit) + break; + result.put(len, result, parens[0][num]); + len++; + } + matched[0] = false; + } + ip[0] = match + matchlen[0]; + + if (version < Context.VERSION_1_3 + && version != Context.VERSION_DEFAULT) + { + /* + * Deviate from ECMA to imitate Perl, which omits a final + * split unless a limit argument is given and big enough. + */ + if (!limited && ip[0] == target.length()) + break; + } + } + return result; + } + + /* + * See ECMA 15.5.4.15 + */ + private static String js_substring(Context cx, String target, + Object[] args) + { + int length = target.length(); + double start = ScriptRuntime.toInteger(args, 0); + double end; + + if (start < 0) + start = 0; + else if (start > length) + start = length; + + if (args.length <= 1 || args[1] == Undefined.instance) { + end = length; + } else { + end = ScriptRuntime.toInteger(args[1]); + if (end < 0) + end = 0; + else if (end > length) + end = length; + + // swap if end < start + if (end < start) { + if (cx.getLanguageVersion() != Context.VERSION_1_2) { + double temp = start; + start = end; + end = temp; + } else { + // Emulate old JDK1.0 java.lang.String.substring() + end = start; + } + } + } + return target.substring((int)start, (int)end); + } + + int getLength() { + return string.length(); + } + + /* + * Non-ECMA methods. + */ + private static String js_substr(String target, Object[] args) { + if (args.length < 1) + return target; + + double begin = ScriptRuntime.toInteger(args[0]); + double end; + int length = target.length(); + + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (args.length == 1) { + end = length; + } else { + end = ScriptRuntime.toInteger(args[1]); + if (end < 0) + end = 0; + end += begin; + if (end > length) + end = length; + } + + return target.substring((int)begin, (int)end); + } + + /* + * Python-esque sequence operations. + */ + private static String js_concat(String target, Object[] args) { + int N = args.length; + if (N == 0) { return target; } + else if (N == 1) { + String arg = ScriptRuntime.toString(args[0]); + return target.concat(arg); + } + + // Find total capacity for the final string to avoid unnecessary + // re-allocations in StringBuffer + int size = target.length(); + String[] argsAsStrings = new String[N]; + for (int i = 0; i != N; ++i) { + String s = ScriptRuntime.toString(args[i]); + argsAsStrings[i] = s; + size += s.length(); + } + + StringBuffer result = new StringBuffer(size); + result.append(target); + for (int i = 0; i != N; ++i) { + result.append(argsAsStrings[i]); + } + return result.toString(); + } + + private static String js_slice(String target, Object[] args) { + if (args.length != 0) { + double begin = ScriptRuntime.toInteger(args[0]); + double end; + int length = target.length(); + if (begin < 0) { + begin += length; + if (begin < 0) + begin = 0; + } else if (begin > length) { + begin = length; + } + + if (args.length == 1) { + end = length; + } else { + end = ScriptRuntime.toInteger(args[1]); + if (end < 0) { + end += length; + if (end < 0) + end = 0; + } else if (end > length) { + end = length; + } + if (end < begin) + end = begin; + } + return target.substring((int)begin, (int)end); + } + return target; + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-01 22:11:49 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 3: c=s.charAt(2); + if (c=='b') { if (s.charAt(0)=='s' && s.charAt(1)=='u') {id=Id_sub; break L0;} } + else if (c=='g') { if (s.charAt(0)=='b' && s.charAt(1)=='i') {id=Id_big; break L0;} } + else if (c=='p') { if (s.charAt(0)=='s' && s.charAt(1)=='u') {id=Id_sup; break L0;} } + break L; + case 4: c=s.charAt(0); + if (c=='b') { X="bold";id=Id_bold; } + else if (c=='l') { X="link";id=Id_link; } + break L; + case 5: switch (s.charAt(4)) { + case 'd': X="fixed";id=Id_fixed; break L; + case 'e': X="slice";id=Id_slice; break L; + case 'h': X="match";id=Id_match; break L; + case 'k': X="blink";id=Id_blink; break L; + case 'l': X="small";id=Id_small; break L; + case 't': X="split";id=Id_split; break L; + } break L; + case 6: switch (s.charAt(1)) { + case 'e': X="search";id=Id_search; break L; + case 'h': X="charAt";id=Id_charAt; break L; + case 'n': X="anchor";id=Id_anchor; break L; + case 'o': X="concat";id=Id_concat; break L; + case 'q': X="equals";id=Id_equals; break L; + case 't': X="strike";id=Id_strike; break L; + case 'u': X="substr";id=Id_substr; break L; + } break L; + case 7: switch (s.charAt(1)) { + case 'a': X="valueOf";id=Id_valueOf; break L; + case 'e': X="replace";id=Id_replace; break L; + case 'n': X="indexOf";id=Id_indexOf; break L; + case 't': X="italics";id=Id_italics; break L; + } break L; + case 8: c=s.charAt(4); + if (c=='r') { X="toString";id=Id_toString; } + else if (c=='s') { X="fontsize";id=Id_fontsize; } + else if (c=='u') { X="toSource";id=Id_toSource; } + break L; + case 9: c=s.charAt(0); + if (c=='f') { X="fontcolor";id=Id_fontcolor; } + else if (c=='s') { X="substring";id=Id_substring; } + break L; + case 10: X="charCodeAt";id=Id_charCodeAt; break L; + case 11: switch (s.charAt(2)) { + case 'L': X="toLowerCase";id=Id_toLowerCase; break L; + case 'U': X="toUpperCase";id=Id_toUpperCase; break L; + case 'n': X="constructor";id=Id_constructor; break L; + case 's': X="lastIndexOf";id=Id_lastIndexOf; break L; + } break L; + case 13: X="localeCompare";id=Id_localeCompare; break L; + case 16: X="equalsIgnoreCase";id=Id_equalsIgnoreCase; break L; + case 17: c=s.charAt(8); + if (c=='L') { X="toLocaleLowerCase";id=Id_toLocaleLowerCase; } + else if (c=='U') { X="toLocaleUpperCase";id=Id_toLocaleUpperCase; } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + ConstructorId_fromCharCode = -1, + + Id_constructor = 1, + Id_toString = 2, + Id_toSource = 3, + Id_valueOf = 4, + Id_charAt = 5, + Id_charCodeAt = 6, + Id_indexOf = 7, + Id_lastIndexOf = 8, + Id_split = 9, + Id_substring = 10, + Id_toLowerCase = 11, + Id_toUpperCase = 12, + Id_substr = 13, + Id_concat = 14, + Id_slice = 15, + Id_bold = 16, + Id_italics = 17, + Id_fixed = 18, + Id_strike = 19, + Id_small = 20, + Id_big = 21, + Id_blink = 22, + Id_sup = 23, + Id_sub = 24, + Id_fontsize = 25, + Id_fontcolor = 26, + Id_link = 27, + Id_anchor = 28, + Id_equals = 29, + Id_equalsIgnoreCase = 30, + Id_match = 31, + Id_search = 32, + Id_replace = 33, + Id_localeCompare = 34, + Id_toLocaleLowerCase = 35, + Id_toLocaleUpperCase = 36, + MAX_PROTOTYPE_ID = 36; + +// #/string_id_map# + + private static final int + ConstructorId_charAt = -Id_charAt, + ConstructorId_charCodeAt = -Id_charCodeAt, + ConstructorId_indexOf = -Id_indexOf, + ConstructorId_lastIndexOf = -Id_lastIndexOf, + ConstructorId_split = -Id_split, + ConstructorId_substring = -Id_substring, + ConstructorId_toLowerCase = -Id_toLowerCase, + ConstructorId_toUpperCase = -Id_toUpperCase, + ConstructorId_substr = -Id_substr, + ConstructorId_concat = -Id_concat, + ConstructorId_slice = -Id_slice, + ConstructorId_equalsIgnoreCase = -Id_equalsIgnoreCase, + ConstructorId_match = -Id_match, + ConstructorId_search = -Id_search, + ConstructorId_replace = -Id_replace, + ConstructorId_localeCompare = -Id_localeCompare, + ConstructorId_toLocaleLowerCase = -Id_toLocaleLowerCase; + + private String string; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeWith.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeWith.java new file mode 100644 index 0000000..83683b2 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NativeWith.java @@ -0,0 +1,207 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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.io.Serializable; + +/** + * This class implements the object lookup required for the + * <code>with</code> statement. + * It simply delegates every action to its prototype except + * for operations on its parent. + */ +public class NativeWith implements Scriptable, IdFunctionCall, Serializable { + + private static final long serialVersionUID = 1L; + + static void init(Scriptable scope, boolean sealed) + { + NativeWith obj = new NativeWith(); + + obj.setParentScope(scope); + obj.setPrototype(ScriptableObject.getObjectPrototype(scope)); + + IdFunctionObject ctor = new IdFunctionObject(obj, FTAG, Id_constructor, + "With", 0, scope); + ctor.markAsConstructor(obj); + if (sealed) { + ctor.sealObject(); + } + ctor.exportAsScopeProperty(); + } + + private NativeWith() { + } + + protected NativeWith(Scriptable parent, Scriptable prototype) { + this.parent = parent; + this.prototype = prototype; + } + + public String getClassName() { + return "With"; + } + + public boolean has(String id, Scriptable start) + { + return prototype.has(id, prototype); + } + + public boolean has(int index, Scriptable start) + { + return prototype.has(index, prototype); + } + + public Object get(String id, Scriptable start) + { + if (start == this) + start = prototype; + return prototype.get(id, start); + } + + public Object get(int index, Scriptable start) + { + if (start == this) + start = prototype; + return prototype.get(index, start); + } + + public void put(String id, Scriptable start, Object value) + { + if (start == this) + start = prototype; + prototype.put(id, start, value); + } + + public void put(int index, Scriptable start, Object value) + { + if (start == this) + start = prototype; + prototype.put(index, start, value); + } + + public void delete(String id) + { + prototype.delete(id); + } + + public void delete(int index) + { + prototype.delete(index); + } + + public Scriptable getPrototype() { + return prototype; + } + + public void setPrototype(Scriptable prototype) { + this.prototype = prototype; + } + + public Scriptable getParentScope() { + return parent; + } + + public void setParentScope(Scriptable parent) { + this.parent = parent; + } + + public Object[] getIds() { + return prototype.getIds(); + } + + public Object getDefaultValue(Class typeHint) { + return prototype.getDefaultValue(typeHint); + } + + public boolean hasInstance(Scriptable value) { + return prototype.hasInstance(value); + } + + /** + * Must return null to continue looping or the final collection result. + */ + protected Object updateDotQuery(boolean value) + { + // NativeWith itself does not support it + throw new IllegalStateException(); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (f.hasTag(FTAG)) { + if (f.methodId() == Id_constructor) { + throw Context.reportRuntimeError1("msg.cant.call.indirect", "With"); + } + } + throw f.unknown(); + } + + static boolean isWithFunction(Object functionObj) + { + if (functionObj instanceof IdFunctionObject) { + IdFunctionObject f = (IdFunctionObject)functionObj; + return f.hasTag(FTAG) && f.methodId() == Id_constructor; + } + return false; + } + + static Object newWithSpecial(Context cx, Scriptable scope, Object[] args) + { + ScriptRuntime.checkDeprecated(cx, "With"); + scope = ScriptableObject.getTopLevelScope(scope); + NativeWith thisObj = new NativeWith(); + thisObj.setPrototype(args.length == 0 + ? ScriptableObject.getClassPrototype(scope, + "Object") + : ScriptRuntime.toObject(cx, scope, args[0])); + thisObj.setParentScope(scope); + return thisObj; + } + + private static final Object FTAG = new Object(); + + private static final int + Id_constructor = 1; + + protected Scriptable prototype; + protected Scriptable parent; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Node.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Node.java new file mode 100644 index 0000000..4298388 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Node.java @@ -0,0 +1,1394 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roshan James + * Roger Lawrence + * Mike McCabe + * + * 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.util.Map; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Collections; + +/** + * This class implements the root of the intermediate representation. + * + * @author Norris Boyd + * @author Mike McCabe + */ + +public class Node +{ + public static final int + FUNCTION_PROP = 1, + LOCAL_PROP = 2, + LOCAL_BLOCK_PROP = 3, + REGEXP_PROP = 4, + CASEARRAY_PROP = 5, + /* + the following properties are defined and manipulated by the + optimizer - + TARGETBLOCK_PROP - the block referenced by a branch node + VARIABLE_PROP - the variable referenced by a BIND or NAME node + ISNUMBER_PROP - this node generates code on Number children and + delivers a Number result (as opposed to Objects) + DIRECTCALL_PROP - this call node should emit code to test the function + object against the known class and call diret if it + matches. + */ + + TARGETBLOCK_PROP = 6, + VARIABLE_PROP = 7, + ISNUMBER_PROP = 8, + DIRECTCALL_PROP = 9, + SPECIALCALL_PROP = 10, + SKIP_INDEXES_PROP = 11, // array of skipped indexes of array literal + OBJECT_IDS_PROP = 12, // array of properties for object literal + INCRDECR_PROP = 13, // pre or post type of increment/decerement + CATCH_SCOPE_PROP = 14, // index of catch scope block in catch + LABEL_ID_PROP = 15, // label id: code generation uses it + MEMBER_TYPE_PROP = 16, // type of element access operation + NAME_PROP = 17, // property name + CONTROL_BLOCK_PROP = 18, // flags a control block that can drop off + PARENTHESIZED_PROP = 19, // expression is parenthesized + GENERATOR_END_PROP = 20, + DESTRUCTURING_ARRAY_LENGTH = 21, + DESTRUCTURING_NAMES= 22, + LAST_PROP = 22; + + // values of ISNUMBER_PROP to specify + // which of the children are Number types + public static final int + BOTH = 0, + LEFT = 1, + RIGHT = 2; + + public static final int // values for SPECIALCALL_PROP + NON_SPECIALCALL = 0, + SPECIALCALL_EVAL = 1, + SPECIALCALL_WITH = 2; + + public static final int // flags for INCRDECR_PROP + DECR_FLAG = 0x1, + POST_FLAG = 0x2; + + public static final int // flags for MEMBER_TYPE_PROP + PROPERTY_FLAG = 0x1, // property access: element is valid name + ATTRIBUTE_FLAG = 0x2, // x.@y or x..@y + DESCENDANTS_FLAG = 0x4; // x..y or x..@i + + private static class NumberNode extends Node + { + NumberNode(double number) + { + super(Token.NUMBER); + this.number = number; + } + + double number; + } + + private static class StringNode extends Node + { + StringNode(int type, String str) { + super(type); + this.str = str; + } + + String str; + Node.Scope scope; + } + + public static class Jump extends Node + { + public Jump(int type) + { + super(type); + } + + Jump(int type, int lineno) + { + super(type, lineno); + } + + Jump(int type, Node child) + { + super(type, child); + } + + Jump(int type, Node child, int lineno) + { + super(type, child, lineno); + } + + public final Jump getJumpStatement() + { + if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug(); + return jumpNode; + } + + public final void setJumpStatement(Jump jumpStatement) + { + if (!(type == Token.BREAK || type == Token.CONTINUE)) Kit.codeBug(); + if (jumpStatement == null) Kit.codeBug(); + if (this.jumpNode != null) Kit.codeBug(); //only once + this.jumpNode = jumpStatement; + } + + public final Node getDefault() + { + if (!(type == Token.SWITCH)) Kit.codeBug(); + return target2; + } + + public final void setDefault(Node defaultTarget) + { + if (!(type == Token.SWITCH)) Kit.codeBug(); + if (defaultTarget.type != Token.TARGET) Kit.codeBug(); + if (target2 != null) Kit.codeBug(); //only once + target2 = defaultTarget; + } + + public final Node getFinally() + { + if (!(type == Token.TRY)) Kit.codeBug(); + return target2; + } + + public final void setFinally(Node finallyTarget) + { + if (!(type == Token.TRY)) Kit.codeBug(); + if (finallyTarget.type != Token.TARGET) Kit.codeBug(); + if (target2 != null) Kit.codeBug(); //only once + target2 = finallyTarget; + } + + public final Jump getLoop() + { + if (!(type == Token.LABEL)) Kit.codeBug(); + return jumpNode; + } + + public final void setLoop(Jump loop) + { + if (!(type == Token.LABEL)) Kit.codeBug(); + if (loop == null) Kit.codeBug(); + if (jumpNode != null) Kit.codeBug(); //only once + jumpNode = loop; + } + + public final Node getContinue() + { + if (type != Token.LOOP) Kit.codeBug(); + return target2; + } + + public final void setContinue(Node continueTarget) + { + if (type != Token.LOOP) Kit.codeBug(); + if (continueTarget.type != Token.TARGET) Kit.codeBug(); + if (target2 != null) Kit.codeBug(); //only once + target2 = continueTarget; + } + + public Node target; + private Node target2; + private Jump jumpNode; + } + + static class Symbol { + Symbol(int declType, String name) { + this.declType = declType; + this.name = name; + this.index = -1; + } + /** + * One of Token.FUNCTION, Token.LP (for parameters), Token.VAR, + * Token.LET, or Token.CONST + */ + int declType; + int index; + String name; + Node.Scope containingTable; + } + + static class Scope extends Jump { + public Scope(int nodeType) { + super(nodeType); + } + + public Scope(int nodeType, int lineno) { + super(nodeType, lineno); + } + + public Scope(int nodeType, Node n, int lineno) { + super(nodeType, n, lineno); + } + + /* + * Creates a new scope node, moving symbol table information + * from "scope" to the new node, and making "scope" a nested + * scope contained by the new node. + * Useful for injecting a new scope in a scope chain. + */ + public static Scope splitScope(Scope scope) { + Scope result = new Scope(scope.getType()); + result.symbolTable = scope.symbolTable; + scope.symbolTable = null; + result.parent = scope.parent; + scope.parent = result; + result.top = scope.top; + return result; + } + + public static void joinScopes(Scope source, Scope dest) { + source.ensureSymbolTable(); + dest.ensureSymbolTable(); + if (!Collections.disjoint(source.symbolTable.keySet(), + dest.symbolTable.keySet())) + { + throw Kit.codeBug(); + } + dest.symbolTable.putAll(source.symbolTable); + } + + public void setParent(Scope parent) { + this.parent = parent; + this.top = parent == null ? (ScriptOrFnNode)this : parent.top; + } + + public Scope getParentScope() { + return parent; + } + + public Scope getDefiningScope(String name) { + for (Scope sn=this; sn != null; sn = sn.parent) { + if (sn.symbolTable == null) + continue; + if (sn.symbolTable.containsKey(name)) + return sn; + } + return null; + } + + public Symbol getSymbol(String name) { + return symbolTable == null ? null : symbolTable.get(name); + } + + public void putSymbol(String name, Symbol symbol) { + ensureSymbolTable(); + symbolTable.put(name, symbol); + symbol.containingTable = this; + top.addSymbol(symbol); + } + + public Map<String,Symbol> getSymbolTable() { + return symbolTable; + } + + private void ensureSymbolTable() { + if (symbolTable == null) { + symbolTable = new LinkedHashMap<String,Symbol>(5); + } + } + + // Use LinkedHashMap so that the iteration order is the insertion order + protected LinkedHashMap<String,Symbol> symbolTable; + private Scope parent; + private ScriptOrFnNode top; + } + + private static class PropListItem + { + PropListItem next; + int type; + int intValue; + Object objectValue; + } + + + public Node(int nodeType) { + type = nodeType; + } + + public Node(int nodeType, Node child) { + type = nodeType; + first = last = child; + child.next = null; + } + + public Node(int nodeType, Node left, Node right) { + type = nodeType; + first = left; + last = right; + left.next = right; + right.next = null; + } + + public Node(int nodeType, Node left, Node mid, Node right) { + type = nodeType; + first = left; + last = right; + left.next = mid; + mid.next = right; + right.next = null; + } + + public Node(int nodeType, int line) { + type = nodeType; + lineno = line; + } + + public Node(int nodeType, Node child, int line) { + this(nodeType, child); + lineno = line; + } + + public Node(int nodeType, Node left, Node right, int line) { + this(nodeType, left, right); + lineno = line; + } + + public Node(int nodeType, Node left, Node mid, Node right, int line) { + this(nodeType, left, mid, right); + lineno = line; + } + + public static Node newNumber(double number) { + return new NumberNode(number); + } + + public static Node newString(String str) { + return new StringNode(Token.STRING, str); + } + + public static Node newString(int type, String str) { + return new StringNode(type, str); + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public boolean hasChildren() { + return first != null; + } + + public Node getFirstChild() { + return first; + } + + public Node getLastChild() { + return last; + } + + public Node getNext() { + return next; + } + + public Node getChildBefore(Node child) { + if (child == first) + return null; + Node n = first; + while (n.next != child) { + n = n.next; + if (n == null) + throw new RuntimeException("node is not a child"); + } + return n; + } + + public Node getLastSibling() { + Node n = this; + while (n.next != null) { + n = n.next; + } + return n; + } + + public void addChildToFront(Node child) { + child.next = first; + first = child; + if (last == null) { + last = child; + } + } + + public void addChildToBack(Node child) { + child.next = null; + if (last == null) { + first = last = child; + return; + } + last.next = child; + last = child; + } + + public void addChildrenToFront(Node children) { + Node lastSib = children.getLastSibling(); + lastSib.next = first; + first = children; + if (last == null) { + last = lastSib; + } + } + + public void addChildrenToBack(Node children) { + if (last != null) { + last.next = children; + } + last = children.getLastSibling(); + if (first == null) { + first = children; + } + } + + /** + * Add 'child' before 'node'. + */ + public void addChildBefore(Node newChild, Node node) { + if (newChild.next != null) + throw new RuntimeException( + "newChild had siblings in addChildBefore"); + if (first == node) { + newChild.next = first; + first = newChild; + return; + } + Node prev = getChildBefore(node); + addChildAfter(newChild, prev); + } + + /** + * Add 'child' after 'node'. + */ + public void addChildAfter(Node newChild, Node node) { + if (newChild.next != null) + throw new RuntimeException( + "newChild had siblings in addChildAfter"); + newChild.next = node.next; + node.next = newChild; + if (last == node) + last = newChild; + } + + public void removeChild(Node child) { + Node prev = getChildBefore(child); + if (prev == null) + first = first.next; + else + prev.next = child.next; + if (child == last) last = prev; + child.next = null; + } + + public void replaceChild(Node child, Node newChild) { + newChild.next = child.next; + if (child == first) { + first = newChild; + } else { + Node prev = getChildBefore(child); + prev.next = newChild; + } + if (child == last) + last = newChild; + child.next = null; + } + + public void replaceChildAfter(Node prevChild, Node newChild) { + Node child = prevChild.next; + newChild.next = child.next; + prevChild.next = newChild; + if (child == last) + last = newChild; + child.next = null; + } + + private static final String propToString(int propType) + { + if (Token.printTrees) { + // If Context.printTrees is false, the compiler + // can remove all these strings. + switch (propType) { + case FUNCTION_PROP: return "function"; + case LOCAL_PROP: return "local"; + case LOCAL_BLOCK_PROP: return "local_block"; + case REGEXP_PROP: return "regexp"; + case CASEARRAY_PROP: return "casearray"; + + case TARGETBLOCK_PROP: return "targetblock"; + case VARIABLE_PROP: return "variable"; + case ISNUMBER_PROP: return "isnumber"; + case DIRECTCALL_PROP: return "directcall"; + + case SPECIALCALL_PROP: return "specialcall"; + case SKIP_INDEXES_PROP: return "skip_indexes"; + case OBJECT_IDS_PROP: return "object_ids_prop"; + case INCRDECR_PROP: return "incrdecr_prop"; + case CATCH_SCOPE_PROP: return "catch_scope_prop"; + case LABEL_ID_PROP: return "label_id_prop"; + case MEMBER_TYPE_PROP: return "member_type_prop"; + case NAME_PROP: return "name_prop"; + case CONTROL_BLOCK_PROP: return "control_block_prop"; + case PARENTHESIZED_PROP: return "parenthesized_prop"; + case GENERATOR_END_PROP: return "generator_end"; + case DESTRUCTURING_ARRAY_LENGTH: + return "destructuring_array_length"; + case DESTRUCTURING_NAMES:return "destructuring_names"; + + default: Kit.codeBug(); + } + } + return null; + } + + private PropListItem lookupProperty(int propType) + { + PropListItem x = propListHead; + while (x != null && propType != x.type) { + x = x.next; + } + return x; + } + + private PropListItem ensureProperty(int propType) + { + PropListItem item = lookupProperty(propType); + if (item == null) { + item = new PropListItem(); + item.type = propType; + item.next = propListHead; + propListHead = item; + } + return item; + } + + public void removeProp(int propType) + { + PropListItem x = propListHead; + if (x != null) { + PropListItem prev = null; + while (x.type != propType) { + prev = x; + x = x.next; + if (x == null) { return; } + } + if (prev == null) { + propListHead = x.next; + } else { + prev.next = x.next; + } + } + } + + public Object getProp(int propType) + { + PropListItem item = lookupProperty(propType); + if (item == null) { return null; } + return item.objectValue; + } + + public int getIntProp(int propType, int defaultValue) + { + PropListItem item = lookupProperty(propType); + if (item == null) { return defaultValue; } + return item.intValue; + } + + public int getExistingIntProp(int propType) + { + PropListItem item = lookupProperty(propType); + if (item == null) { Kit.codeBug(); } + return item.intValue; + } + + public void putProp(int propType, Object prop) + { + if (prop == null) { + removeProp(propType); + } else { + PropListItem item = ensureProperty(propType); + item.objectValue = prop; + } + } + + public void putIntProp(int propType, int prop) + { + PropListItem item = ensureProperty(propType); + item.intValue = prop; + } + + public int getLineno() { + return lineno; + } + + /** Can only be called when <tt>getType() == Token.NUMBER</tt> */ + public final double getDouble() { + return ((NumberNode)this).number; + } + + public final void setDouble(double number) { + ((NumberNode)this).number = number; + } + + /** Can only be called when node has String context. */ + public final String getString() { + return ((StringNode)this).str; + } + + /** Can only be called when node has String context. */ + public final void setString(String s) { + if (s == null) Kit.codeBug(); + ((StringNode)this).str = s; + } + + /** Can only be called when node has String context. */ + public final Scope getScope() { + return ((StringNode)this).scope; + } + + /** Can only be called when node has String context. */ + public final void setScope(Scope s) { + if (s == null) Kit.codeBug(); + if (!(this instanceof StringNode)) { + throw Kit.codeBug(); + } + ((StringNode)this).scope = s; + } + + public static Node newTarget() + { + return new Node(Token.TARGET); + } + + public final int labelId() + { + if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug(); + return getIntProp(LABEL_ID_PROP, -1); + } + + public void labelId(int labelId) + { + if (type != Token.TARGET && type != Token.YIELD) Kit.codeBug(); + putIntProp(LABEL_ID_PROP, labelId); + } + + + /** + * Does consistent-return analysis on the function body when strict mode is + * enabled. + * + * function (x) { return (x+1) } + * is ok, but + * function (x) { if (x < 0) return (x+1); } + * is not becuase the function can potentially return a value when the + * condition is satisfied and if not, the function does not explicitly + * return value. + * + * This extends to checking mismatches such as "return" and "return <value>" + * used in the same function. Warnings are not emitted if inconsistent + * returns exist in code that can be statically shown to be unreachable. + * Ex. + * function (x) { while (true) { ... if (..) { return value } ... } } + * emits no warning. However if the loop had a break statement, then a + * warning would be emitted. + * + * The consistency analysis looks at control structures such as loops, ifs, + * switch, try-catch-finally blocks, examines the reachable code paths and + * warns the user about an inconsistent set of termination possibilities. + * + * Caveat: Since the parser flattens many control structures into almost + * straight-line code with gotos, it makes such analysis hard. Hence this + * analyser is written to taken advantage of patterns of code generated by + * the parser (for loops, try blocks and such) and does not do a full + * control flow analysis of the gotos and break/continue statements. + * Future changes to the parser will affect this analysis. + */ + + /** + * These flags enumerate the possible ways a statement/function can + * terminate. These flags are used by endCheck() and by the Parser to + * detect inconsistent return usage. + * + * END_UNREACHED is reserved for code paths that are assumed to always be + * able to execute (example: throw, continue) + * + * END_DROPS_OFF indicates if the statement can transfer control to the + * next one. Statement such as return dont. A compound statement may have + * some branch that drops off control to the next statement. + * + * END_RETURNS indicates that the statement can return (without arguments) + * END_RETURNS_VALUE indicates that the statement can return a value. + * + * A compound statement such as + * if (condition) { + * return value; + * } + * Will be detected as (END_DROPS_OFF | END_RETURN_VALUE) by endCheck() + */ + static final int END_UNREACHED = 0; + static final int END_DROPS_OFF = 1; + static final int END_RETURNS = 2; + static final int END_RETURNS_VALUE = 4; + static final int END_YIELDS = 8; + + /** + * Checks that every return usage in a function body is consistent with the + * requirements of strict-mode. + * @return true if the function satisfies strict mode requirement. + */ + public boolean hasConsistentReturnUsage() + { + int n = endCheck(); + return (n & END_RETURNS_VALUE) == 0 || + (n & (END_DROPS_OFF|END_RETURNS|END_YIELDS)) == 0; + } + + /** + * Returns in the then and else blocks must be consistent with each other. + * If there is no else block, then the return statement can fall through. + * @return logical OR of END_* flags + */ + private int endCheckIf() + { + Node th, el; + int rv = END_UNREACHED; + + th = next; + el = ((Jump)this).target; + + rv = th.endCheck(); + + if (el != null) + rv |= el.endCheck(); + else + rv |= END_DROPS_OFF; + + return rv; + } + + /** + * Consistency of return statements is checked between the case statements. + * If there is no default, then the switch can fall through. If there is a + * default,we check to see if all code paths in the default return or if + * there is a code path that can fall through. + * @return logical OR of END_* flags + */ + private int endCheckSwitch() + { + Node n; + int rv = END_UNREACHED; + + // examine the cases + for (n = first.next; n != null; n = n.next) + { + if (n.type == Token.CASE) { + rv |= ((Jump)n).target.endCheck(); + } else + break; + } + + // we don't care how the cases drop into each other + rv &= ~END_DROPS_OFF; + + // examine the default + n = ((Jump)this).getDefault(); + if (n != null) + rv |= n.endCheck(); + else + rv |= END_DROPS_OFF; + + // remove the switch block + rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED); + + return rv; + } + + /** + * If the block has a finally, return consistency is checked in the + * finally block. If all code paths in the finally returns, then the + * returns in the try-catch blocks don't matter. If there is a code path + * that does not return or if there is no finally block, the returns + * of the try and catch blocks are checked for mismatch. + * @return logical OR of END_* flags + */ + private int endCheckTry() + { + Node n; + int rv = END_UNREACHED; + + // check the finally if it exists + n = ((Jump)this).getFinally(); + if(n != null) { + rv = n.next.first.endCheck(); + } else { + rv = END_DROPS_OFF; + } + + // if the finally block always returns, then none of the returns + // in the try or catch blocks matter + if ((rv & END_DROPS_OFF) != 0) { + rv &= ~END_DROPS_OFF; + + // examine the try block + rv |= first.endCheck(); + + // check each catch block + n = ((Jump)this).target; + if (n != null) + { + // point to the first catch_scope + for (n = n.next.first; n != null; n = n.next.next) + { + // check the block of user code in the catch_scope + rv |= n.next.first.next.first.endCheck(); + } + } + } + + return rv; + } + + /** + * Return statement in the loop body must be consistent. The default + * assumption for any kind of a loop is that it will eventually terminate. + * The only exception is a loop with a constant true condition. Code that + * follows such a loop is examined only if one can statically determine + * that there is a break out of the loop. + * for(<> ; <>; <>) {} + * for(<> in <> ) {} + * while(<>) { } + * do { } while(<>) + * @return logical OR of END_* flags + */ + private int endCheckLoop() + { + Node n; + int rv = END_UNREACHED; + + // To find the loop body, we look at the second to last node of the + // loop node, which should be the predicate that the loop should + // satisfy. + // The target of the predicate is the loop-body for all 4 kinds of + // loops. + for (n = first; n.next != last; n = n.next) { + /* skip */ + } + if (n.type != Token.IFEQ) + return END_DROPS_OFF; + + // The target's next is the loop body block + rv = ((Jump)n).target.next.endCheck(); + + // check to see if the loop condition is true + if (n.first.type == Token.TRUE) + rv &= ~END_DROPS_OFF; + + // look for effect of breaks + rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED); + + return rv; + } + + + /** + * A general block of code is examined statement by statement. If any + * statement (even compound ones) returns in all branches, then subsequent + * statements are not examined. + * @return logical OR of END_* flags + */ + private int endCheckBlock() + { + Node n; + int rv = END_DROPS_OFF; + + // check each statment and if the statement can continue onto the next + // one, then check the next statement + for (n=first; ((rv & END_DROPS_OFF) != 0) && n != null; n = n.next) + { + rv &= ~END_DROPS_OFF; + rv |= n.endCheck(); + } + return rv; + } + + /** + * A labelled statement implies that there maybe a break to the label. The + * function processes the labelled statement and then checks the + * CONTROL_BLOCK_PROP property to see if there is ever a break to the + * particular label. + * @return logical OR of END_* flags + */ + private int endCheckLabel() + { + int rv = END_UNREACHED; + + rv = next.endCheck(); + rv |= getIntProp(CONTROL_BLOCK_PROP, END_UNREACHED); + + return rv; + } + + /** + * When a break is encountered annotate the statement being broken + * out of by setting its CONTROL_BLOCK_PROP property. + * @return logical OR of END_* flags + */ + private int endCheckBreak() + { + Node n = ((Jump) this).jumpNode; + n.putIntProp(CONTROL_BLOCK_PROP, END_DROPS_OFF); + return END_UNREACHED; + } + + /** + * endCheck() examines the body of a function, doing a basic reachability + * analysis and returns a combination of flags END_* flags that indicate + * how the function execution can terminate. These constitute only the + * pessimistic set of termination conditions. It is possible that at + * runtime certain code paths will never be actually taken. Hence this + * analysis will flag errors in cases where there may not be errors. + * @return logical OR of END_* flags + */ + private int endCheck() + { + switch(type) + { + case Token.BREAK: + return endCheckBreak(); + + case Token.EXPR_VOID: + if (this.first != null) + return first.endCheck(); + return END_DROPS_OFF; + + case Token.YIELD: + return END_YIELDS; + + case Token.CONTINUE: + case Token.THROW: + return END_UNREACHED; + + case Token.RETURN: + if (this.first != null) + return END_RETURNS_VALUE; + else + return END_RETURNS; + + case Token.TARGET: + if (next != null) + return next.endCheck(); + else + return END_DROPS_OFF; + + case Token.LOOP: + return endCheckLoop(); + + case Token.LOCAL_BLOCK: + case Token.BLOCK: + // there are several special kinds of blocks + if (first == null) + return END_DROPS_OFF; + + switch(first.type) { + case Token.LABEL: + return first.endCheckLabel(); + + case Token.IFNE: + return first.endCheckIf(); + + case Token.SWITCH: + return first.endCheckSwitch(); + + case Token.TRY: + return first.endCheckTry(); + + default: + return endCheckBlock(); + } + + default: + return END_DROPS_OFF; + } + } + + public boolean hasSideEffects() + { + switch (type) { + case Token.EXPR_VOID: + case Token.COMMA: + if (last != null) + return last.hasSideEffects(); + else + return true; + + case Token.HOOK: + if (first == null || + first.next == null || + first.next.next == null) + Kit.codeBug(); + return first.next.hasSideEffects() && + first.next.next.hasSideEffects(); + + case Token.ERROR: // Avoid cascaded error messages + case Token.EXPR_RESULT: + case Token.ASSIGN: + case Token.ASSIGN_ADD: + case Token.ASSIGN_SUB: + case Token.ASSIGN_MUL: + case Token.ASSIGN_DIV: + case Token.ASSIGN_MOD: + case Token.ASSIGN_BITOR: + case Token.ASSIGN_BITXOR: + case Token.ASSIGN_BITAND: + case Token.ASSIGN_LSH: + case Token.ASSIGN_RSH: + case Token.ASSIGN_URSH: + case Token.ENTERWITH: + case Token.LEAVEWITH: + case Token.RETURN: + case Token.GOTO: + case Token.IFEQ: + case Token.IFNE: + case Token.NEW: + case Token.DELPROP: + case Token.SETNAME: + case Token.SETPROP: + case Token.SETELEM: + case Token.CALL: + case Token.THROW: + case Token.RETHROW: + case Token.SETVAR: + case Token.CATCH_SCOPE: + case Token.RETURN_RESULT: + case Token.SET_REF: + case Token.DEL_REF: + case Token.REF_CALL: + case Token.TRY: + case Token.SEMI: + case Token.INC: + case Token.DEC: + case Token.EXPORT: + case Token.IMPORT: + case Token.IF: + case Token.ELSE: + case Token.SWITCH: + case Token.WHILE: + case Token.DO: + case Token.FOR: + case Token.BREAK: + case Token.CONTINUE: + case Token.VAR: + case Token.CONST: + case Token.LET: + case Token.LETEXPR: + case Token.WITH: + case Token.WITHEXPR: + case Token.CATCH: + case Token.FINALLY: + case Token.BLOCK: + case Token.LABEL: + case Token.TARGET: + case Token.LOOP: + case Token.JSR: + case Token.SETPROP_OP: + case Token.SETELEM_OP: + case Token.LOCAL_BLOCK: + case Token.SET_REF_OP: + case Token.YIELD: + return true; + + default: + return false; + } + } + + public String toString() + { + if (Token.printTrees) { + StringBuffer sb = new StringBuffer(); + toString(new ObjToIntMap(), sb); + return sb.toString(); + } + return String.valueOf(type); + } + + private void toString(ObjToIntMap printIds, StringBuffer sb) + { + if (Token.printTrees) { + sb.append(Token.name(type)); + if (this instanceof StringNode) { + sb.append(' '); + sb.append(getString()); + Scope scope = getScope(); + if (scope != null) { + sb.append("[scope: "); + appendPrintId(scope, printIds, sb); + sb.append("]"); + } + } else if (this instanceof Node.Scope) { + if (this instanceof ScriptOrFnNode) { + ScriptOrFnNode sof = (ScriptOrFnNode)this; + if (this instanceof FunctionNode) { + FunctionNode fn = (FunctionNode)this; + sb.append(' '); + sb.append(fn.getFunctionName()); + } + sb.append(" [source name: "); + sb.append(sof.getSourceName()); + sb.append("] [encoded source length: "); + sb.append(sof.getEncodedSourceEnd() + - sof.getEncodedSourceStart()); + sb.append("] [base line: "); + sb.append(sof.getBaseLineno()); + sb.append("] [end line: "); + sb.append(sof.getEndLineno()); + sb.append(']'); + } + if (((Node.Scope)this).symbolTable != null) { + sb.append(" [scope "); + appendPrintId(this, printIds, sb); + sb.append(": "); + Iterator iter = ((Node.Scope) this).symbolTable.keySet() + .iterator(); + while (iter.hasNext()) { + sb.append(iter.next()); + sb.append(" "); + } + sb.append("]"); + } + } else if (this instanceof Jump) { + Jump jump = (Jump)this; + if (type == Token.BREAK || type == Token.CONTINUE) { + sb.append(" [label: "); + appendPrintId(jump.getJumpStatement(), printIds, sb); + sb.append(']'); + } else if (type == Token.TRY) { + Node catchNode = jump.target; + Node finallyTarget = jump.getFinally(); + if (catchNode != null) { + sb.append(" [catch: "); + appendPrintId(catchNode, printIds, sb); + sb.append(']'); + } + if (finallyTarget != null) { + sb.append(" [finally: "); + appendPrintId(finallyTarget, printIds, sb); + sb.append(']'); + } + } else if (type == Token.LABEL || type == Token.LOOP + || type == Token.SWITCH) + { + sb.append(" [break: "); + appendPrintId(jump.target, printIds, sb); + sb.append(']'); + if (type == Token.LOOP) { + sb.append(" [continue: "); + appendPrintId(jump.getContinue(), printIds, sb); + sb.append(']'); + } + } else { + sb.append(" [target: "); + appendPrintId(jump.target, printIds, sb); + sb.append(']'); + } + } else if (type == Token.NUMBER) { + sb.append(' '); + sb.append(getDouble()); + } else if (type == Token.TARGET) { + sb.append(' '); + appendPrintId(this, printIds, sb); + } + if (lineno != -1) { + sb.append(' '); + sb.append(lineno); + } + + for (PropListItem x = propListHead; x != null; x = x.next) { + int type = x.type; + sb.append(" ["); + sb.append(propToString(type)); + sb.append(": "); + String value; + switch (type) { + case TARGETBLOCK_PROP : // can't add this as it recurses + value = "target block property"; + break; + case LOCAL_BLOCK_PROP : // can't add this as it is dull + value = "last local block"; + break; + case ISNUMBER_PROP: + switch (x.intValue) { + case BOTH: + value = "both"; + break; + case RIGHT: + value = "right"; + break; + case LEFT: + value = "left"; + break; + default: + throw Kit.codeBug(); + } + break; + case SPECIALCALL_PROP: + switch (x.intValue) { + case SPECIALCALL_EVAL: + value = "eval"; + break; + case SPECIALCALL_WITH: + value = "with"; + break; + default: + // NON_SPECIALCALL should not be stored + throw Kit.codeBug(); + } + break; + case OBJECT_IDS_PROP: { + Object[] a = (Object[]) x.objectValue; + value = "["; + for (int i=0; i < a.length; i++) { + value += a[i].toString(); + if (i+1 < a.length) + value += ", "; + } + value += "]"; + break; + } + default : + Object obj = x.objectValue; + if (obj != null) { + value = obj.toString(); + } else { + value = String.valueOf(x.intValue); + } + break; + } + sb.append(value); + sb.append(']'); + } + } + } + + public String toStringTree(ScriptOrFnNode treeTop) { + if (Token.printTrees) { + StringBuffer sb = new StringBuffer(); + toStringTreeHelper(treeTop, this, null, 0, sb); + return sb.toString(); + } + return null; + } + + private static void toStringTreeHelper(ScriptOrFnNode treeTop, Node n, + ObjToIntMap printIds, + int level, StringBuffer sb) + { + if (Token.printTrees) { + if (printIds == null) { + printIds = new ObjToIntMap(); + generatePrintIds(treeTop, printIds); + } + for (int i = 0; i != level; ++i) { + sb.append(" "); + } + n.toString(printIds, sb); + sb.append('\n'); + for (Node cursor = n.getFirstChild(); cursor != null; + cursor = cursor.getNext()) + { + if (cursor.getType() == Token.FUNCTION) { + int fnIndex = cursor.getExistingIntProp(Node.FUNCTION_PROP); + FunctionNode fn = treeTop.getFunctionNode(fnIndex); + toStringTreeHelper(fn, fn, null, level + 1, sb); + } else { + toStringTreeHelper(treeTop, cursor, printIds, level + 1, sb); + } + } + } + } + + private static void generatePrintIds(Node n, ObjToIntMap map) + { + if (Token.printTrees) { + map.put(n, map.size()); + for (Node cursor = n.getFirstChild(); cursor != null; + cursor = cursor.getNext()) + { + generatePrintIds(cursor, map); + } + } + } + + private static void appendPrintId(Node n, ObjToIntMap printIds, + StringBuffer sb) + { + if (Token.printTrees) { + if (n != null) { + int id = printIds.get(n, -1); + sb.append('#'); + if (id != -1) { + sb.append(id + 1); + } else { + sb.append("<not_available>"); + } + } + } + } + + int type; // type of the node; Token.NAME for example + Node next; // next sibling + private Node first; // first element of a linked list of children + private Node last; // last element of a linked list of children + protected int lineno = -1; + + /*APPJET*/public int statementEndLineNum = -1; + + /** + * Linked list of properties. Since vast majority of nodes would have + * no more then 2 properties, linked list saves memory and provides + * fast lookup. If this does not holds, propListHead can be replaced + * by UintMap. + */ + private PropListItem propListHead; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NodeTransformer.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NodeTransformer.java new file mode 100644 index 0000000..201c6f2 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/NodeTransformer.java @@ -0,0 +1,565 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Mike McCabe + * + * 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.util.ArrayList; +import java.util.List; + +/** + * This class transforms a tree to a lower-level representation for codegen. + * + * @see Node + * @author Norris Boyd + */ + +public class NodeTransformer +{ + + public NodeTransformer() + { + } + + public final void transform(ScriptOrFnNode tree) + { + transformCompilationUnit(tree); + for (int i = 0; i != tree.getFunctionCount(); ++i) { + FunctionNode fn = tree.getFunctionNode(i); + transform(fn); + } + } + + private void transformCompilationUnit(ScriptOrFnNode tree) + { + loops = new ObjArray(); + loopEnds = new ObjArray(); + + // to save against upchecks if no finally blocks are used. + hasFinally = false; + + // Flatten all only if we are not using scope objects for block scope + boolean createScopeObjects = tree.getType() != Token.FUNCTION || + ((FunctionNode)tree).requiresActivation(); + tree.flattenSymbolTable(!createScopeObjects); + + //uncomment to print tree before transformation + //if (Token.printTrees) System.out.println(tree.toStringTree(tree)); + transformCompilationUnit_r(tree, tree, tree, createScopeObjects); + } + + private void transformCompilationUnit_r(final ScriptOrFnNode tree, + final Node parent, + Node.Scope scope, + boolean createScopeObjects) + { + Node node = null; + siblingLoop: + for (;;) { + Node previous = null; + if (node == null) { + node = parent.getFirstChild(); + } else { + previous = node; + node = node.getNext(); + } + if (node == null) { + break; + } + + int type = node.getType(); + if (createScopeObjects && + (type == Token.BLOCK || type == Token.LOOP || + type == Token.ARRAYCOMP) && + (node instanceof Node.Scope)) + { + Node.Scope newScope = (Node.Scope) node; + if (newScope.symbolTable != null) { + // transform to let statement so we get a with statement + // created to contain scoped let variables + Node let = new Node(type == Token.ARRAYCOMP ? Token.LETEXPR + : Token.LET); + Node innerLet = new Node(Token.LET); + let.addChildToBack(innerLet); + for (String name: newScope.symbolTable.keySet()) { + innerLet.addChildToBack(Node.newString(Token.NAME, name)); + } + newScope.symbolTable = null; // so we don't transform again + Node oldNode = node; + node = replaceCurrent(parent, previous, node, let); + type = node.getType(); + let.addChildToBack(oldNode); + } + } + + switch (type) { + + case Token.LABEL: + case Token.SWITCH: + case Token.LOOP: + loops.push(node); + loopEnds.push(((Node.Jump)node).target); + break; + + case Token.WITH: + { + loops.push(node); + Node leave = node.getNext(); + if (leave.getType() != Token.LEAVEWITH) { + Kit.codeBug(); + } + loopEnds.push(leave); + break; + } + + case Token.TRY: + { + Node.Jump jump = (Node.Jump)node; + Node finallytarget = jump.getFinally(); + if (finallytarget != null) { + hasFinally = true; + loops.push(node); + loopEnds.push(finallytarget); + } + break; + } + + case Token.TARGET: + case Token.LEAVEWITH: + if (!loopEnds.isEmpty() && loopEnds.peek() == node) { + loopEnds.pop(); + loops.pop(); + } + break; + + case Token.YIELD: + ((FunctionNode)tree).addResumptionPoint(node); + break; + + case Token.RETURN: + { + boolean isGenerator = tree.getType() == Token.FUNCTION + && ((FunctionNode)tree).isGenerator(); + if (isGenerator) { + node.putIntProp(Node.GENERATOR_END_PROP, 1); + } + /* If we didn't support try/finally, it wouldn't be + * necessary to put LEAVEWITH nodes here... but as + * we do need a series of JSR FINALLY nodes before + * each RETURN, we need to ensure that each finally + * block gets the correct scope... which could mean + * that some LEAVEWITH nodes are necessary. + */ + if (!hasFinally) + break; // skip the whole mess. + Node unwindBlock = null; + for (int i=loops.size()-1; i >= 0; i--) { + Node n = (Node) loops.get(i); + int elemtype = n.getType(); + if (elemtype == Token.TRY || elemtype == Token.WITH) { + Node unwind; + if (elemtype == Token.TRY) { + Node.Jump jsrnode = new Node.Jump(Token.JSR); + Node jsrtarget = ((Node.Jump)n).getFinally(); + jsrnode.target = jsrtarget; + unwind = jsrnode; + } else { + unwind = new Node(Token.LEAVEWITH); + } + if (unwindBlock == null) { + unwindBlock = new Node(Token.BLOCK, + node.getLineno()); + } + unwindBlock.addChildToBack(unwind); + } + } + if (unwindBlock != null) { + Node returnNode = node; + Node returnExpr = returnNode.getFirstChild(); + node = replaceCurrent(parent, previous, node, unwindBlock); + if (returnExpr == null || isGenerator) { + unwindBlock.addChildToBack(returnNode); + } else { + Node store = new Node(Token.EXPR_RESULT, returnExpr); + unwindBlock.addChildToFront(store); + returnNode = new Node(Token.RETURN_RESULT); + unwindBlock.addChildToBack(returnNode); + // transform return expression + transformCompilationUnit_r(tree, store, scope, + createScopeObjects); + } + // skip transformCompilationUnit_r to avoid infinite loop + continue siblingLoop; + } + break; + } + + case Token.BREAK: + case Token.CONTINUE: + { + Node.Jump jump = (Node.Jump)node; + Node.Jump jumpStatement = jump.getJumpStatement(); + if (jumpStatement == null) Kit.codeBug(); + + for (int i = loops.size(); ;) { + if (i == 0) { + // Parser/IRFactory ensure that break/continue + // always has a jump statement associated with it + // which should be found + throw Kit.codeBug(); + } + --i; + Node n = (Node) loops.get(i); + if (n == jumpStatement) { + break; + } + + int elemtype = n.getType(); + if (elemtype == Token.WITH) { + Node leave = new Node(Token.LEAVEWITH); + previous = addBeforeCurrent(parent, previous, node, + leave); + } else if (elemtype == Token.TRY) { + Node.Jump tryNode = (Node.Jump)n; + Node.Jump jsrFinally = new Node.Jump(Token.JSR); + jsrFinally.target = tryNode.getFinally(); + previous = addBeforeCurrent(parent, previous, node, + jsrFinally); + } + } + + if (type == Token.BREAK) { + jump.target = jumpStatement.target; + } else { + jump.target = jumpStatement.getContinue(); + } + jump.setType(Token.GOTO); + + break; + } + + case Token.CALL: + visitCall(node, tree); + break; + + case Token.NEW: + visitNew(node, tree); + break; + + case Token.LETEXPR: + case Token.LET: { + Node child = node.getFirstChild(); + if (child.getType() == Token.LET) { + // We have a let statement or expression rather than a + // let declaration + boolean createWith = tree.getType() != Token.FUNCTION + || ((FunctionNode)tree).requiresActivation(); + node = visitLet(createWith, parent, previous, node); + break; + } else { + // fall through to process let declaration... + } + } + /* fall through */ + case Token.CONST: + case Token.VAR: + { + Node result = new Node(Token.BLOCK); + for (Node cursor = node.getFirstChild(); cursor != null;) { + // Move cursor to next before createAssignment gets chance + // to change n.next + Node n = cursor; + cursor = cursor.getNext(); + if (n.getType() == Token.NAME) { + if (!n.hasChildren()) + continue; + Node init = n.getFirstChild(); + n.removeChild(init); + n.setType(Token.BINDNAME); + n = new Node(type == Token.CONST ? + Token.SETCONST : + Token.SETNAME, + n, init); + } else { + // May be a destructuring assignment already transformed + // to a LETEXPR + if (n.getType() != Token.LETEXPR) + throw Kit.codeBug(); + } + Node pop = new Node(Token.EXPR_VOID, n, node.getLineno()); + result.addChildToBack(pop); + } + node = replaceCurrent(parent, previous, node, result); + break; + } + + case Token.TYPEOFNAME: { + Node.Scope defining = scope.getDefiningScope(node.getString()); + if (defining != null) { + node.setScope(defining); + } + } + break; + + case Token.TYPEOF: + case Token.IFNE: { + /* We want to suppress warnings for undefined property o.p + * for the following constructs: typeof o.p, if (o.p), + * if (!o.p), if (o.p == undefined), if (undefined == o.p) + */ + Node child = node.getFirstChild(); + if (type == Token.IFNE) { + while (child.getType() == Token.NOT) { + child = child.getFirstChild(); + } + if (child.getType() == Token.EQ || + child.getType() == Token.NE) + { + Node first = child.getFirstChild(); + Node last = child.getLastChild(); + if (first.getType() == Token.NAME && + first.getString().equals("undefined")) + child = last; + else if (last.getType() == Token.NAME && + last.getString().equals("undefined")) + child = first; + } + } + if (child.getType() == Token.GETPROP) + child.setType(Token.GETPROPNOWARN); + break; + } + + case Token.NAME: + case Token.SETNAME: + case Token.SETCONST: + case Token.DELPROP: + { + // Turn name to var for faster access if possible + if (createScopeObjects) { + break; + } + Node nameSource; + if (type == Token.NAME) { + nameSource = node; + } else { + nameSource = node.getFirstChild(); + if (nameSource.getType() != Token.BINDNAME) { + if (type == Token.DELPROP) { + break; + } + throw Kit.codeBug(); + } + } + if (nameSource.getScope() != null) { + break; // already have a scope set + } + String name = nameSource.getString(); + Node.Scope defining = scope.getDefiningScope(name); + if (defining != null) { + nameSource.setScope(defining); + if (type == Token.NAME) { + node.setType(Token.GETVAR); + } else if (type == Token.SETNAME) { + node.setType(Token.SETVAR); + nameSource.setType(Token.STRING); + } else if (type == Token.SETCONST) { + node.setType(Token.SETCONSTVAR); + nameSource.setType(Token.STRING); + } else if (type == Token.DELPROP) { + // Local variables are by definition permanent + Node n = new Node(Token.FALSE); + node = replaceCurrent(parent, previous, node, n); + } else { + throw Kit.codeBug(); + } + } + break; + } + } + + transformCompilationUnit_r(tree, node, + node instanceof Node.Scope ? (Node.Scope)node : scope, + createScopeObjects); + } + } + + protected void visitNew(Node node, ScriptOrFnNode tree) { + } + + protected void visitCall(Node node, ScriptOrFnNode tree) { + } + + protected Node visitLet(boolean createWith, Node parent, Node previous, + Node scopeNode) + { + Node vars = scopeNode.getFirstChild(); + Node body = vars.getNext(); + scopeNode.removeChild(vars); + scopeNode.removeChild(body); + boolean isExpression = scopeNode.getType() == Token.LETEXPR; + Node result; + Node newVars; + if (createWith) { + result = new Node(isExpression ? Token.WITHEXPR : Token.BLOCK); + result = replaceCurrent(parent, previous, scopeNode, result); + ArrayList<Object> list = new ArrayList<Object>(); + Node objectLiteral = new Node(Token.OBJECTLIT); + for (Node v=vars.getFirstChild(); v != null; v = v.getNext()) { + Node current = v; + if (current.getType() == Token.LETEXPR) { + // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {} + List<?> destructuringNames = (List<?>) + current.getProp(Node.DESTRUCTURING_NAMES); + Node c = current.getFirstChild(); + if (c.getType() != Token.LET) throw Kit.codeBug(); + // Add initialization code to front of body + if (isExpression) { + body = new Node(Token.COMMA, c.getNext(), body); + } else { + body = new Node(Token.BLOCK, + new Node(Token.EXPR_VOID, c.getNext()), + body); + } + // Update "list" and "objectLiteral" for the variables + // defined in the destructuring assignment + if (destructuringNames != null) { + list.addAll(destructuringNames); + for (int i=0; i < destructuringNames.size(); i++) { + objectLiteral.addChildToBack( + new Node(Token.VOID, Node.newNumber(0.0))); + } + } + current = c.getFirstChild(); // should be a NAME, checked below + } + if (current.getType() != Token.NAME) throw Kit.codeBug(); + list.add(ScriptRuntime.getIndexObject(current.getString())); + Node init = current.getFirstChild(); + if (init == null) { + init = new Node(Token.VOID, Node.newNumber(0.0)); + } + objectLiteral.addChildToBack(init); + } + objectLiteral.putProp(Node.OBJECT_IDS_PROP, list.toArray()); + newVars = new Node(Token.ENTERWITH, objectLiteral); + result.addChildToBack(newVars); + result.addChildToBack(new Node(Token.WITH, body)); + result.addChildToBack(new Node(Token.LEAVEWITH)); + } else { + result = new Node(isExpression ? Token.COMMA : Token.BLOCK); + result = replaceCurrent(parent, previous, scopeNode, result); + newVars = new Node(Token.COMMA); + for (Node v=vars.getFirstChild(); v != null; v = v.getNext()) { + Node current = v; + if (current.getType() == Token.LETEXPR) { + // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {} + Node c = current.getFirstChild(); + if (c.getType() != Token.LET) throw Kit.codeBug(); + // Add initialization code to front of body + if (isExpression) { + body = new Node(Token.COMMA, c.getNext(), body); + } else { + body = new Node(Token.BLOCK, + new Node(Token.EXPR_VOID, c.getNext()), + body); + } + // We're removing the LETEXPR, so move the symbols + Node.Scope.joinScopes((Node.Scope)current, + (Node.Scope)scopeNode); + current = c.getFirstChild(); // should be a NAME, checked below + } + if (current.getType() != Token.NAME) throw Kit.codeBug(); + Node stringNode = Node.newString(current.getString()); + stringNode.setScope((Node.Scope)scopeNode); + Node init = current.getFirstChild(); + if (init == null) { + init = new Node(Token.VOID, Node.newNumber(0.0)); + } + newVars.addChildToBack(new Node(Token.SETVAR, stringNode, init)); + } + if (isExpression) { + result.addChildToBack(newVars); + scopeNode.setType(Token.COMMA); + result.addChildToBack(scopeNode); + scopeNode.addChildToBack(body); + } else { + result.addChildToBack(new Node(Token.EXPR_VOID, newVars)); + scopeNode.setType(Token.BLOCK); + result.addChildToBack(scopeNode); + scopeNode.addChildrenToBack(body); + } + } + return result; + } + + private static Node addBeforeCurrent(Node parent, Node previous, + Node current, Node toAdd) + { + if (previous == null) { + if (!(current == parent.getFirstChild())) Kit.codeBug(); + parent.addChildToFront(toAdd); + } else { + if (!(current == previous.getNext())) Kit.codeBug(); + parent.addChildAfter(toAdd, previous); + } + return toAdd; + } + + private static Node replaceCurrent(Node parent, Node previous, + Node current, Node replacement) + { + if (previous == null) { + if (!(current == parent.getFirstChild())) Kit.codeBug(); + parent.replaceChild(current, replacement); + } else if (previous.next == current) { + // Check cachedPrev.next == current is necessary due to possible + // tree mutations + parent.replaceChildAfter(previous, replacement); + } else { + parent.replaceChild(current, replacement); + } + return replacement; + } + + private ObjArray loops; + private ObjArray loopEnds; + private boolean hasFinally; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjArray.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjArray.java new file mode 100644 index 0000000..a9636a3 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjArray.java @@ -0,0 +1,388 @@ +/* -*- 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): + * Igor Bukanov + * + * 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.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** +Implementation of resizable array with focus on minimizing memory usage by storing few initial array elements in object fields. Can also be used as a stack. +*/ + +public class ObjArray implements Serializable +{ + static final long serialVersionUID = 4174889037736658296L; + + public ObjArray() { } + + public final boolean isSealed() + { + return sealed; + } + + public final void seal() + { + sealed = true; + } + + public final boolean isEmpty() + { + return size == 0; + } + + public final int size() + { + return size; + } + + public final void setSize(int newSize) + { + if (newSize < 0) throw new IllegalArgumentException(); + if (sealed) throw onSeledMutation(); + int N = size; + if (newSize < N) { + for (int i = newSize; i != N; ++i) { + setImpl(i, null); + } + } else if (newSize > N) { + if (newSize > FIELDS_STORE_SIZE) { + ensureCapacity(newSize); + } + } + size = newSize; + } + + public final Object get(int index) + { + if (!(0 <= index && index < size)) throw onInvalidIndex(index, size); + return getImpl(index); + } + + public final void set(int index, Object value) + { + if (!(0 <= index && index < size)) throw onInvalidIndex(index, size); + if (sealed) throw onSeledMutation(); + setImpl(index, value); + } + + private Object getImpl(int index) + { + switch (index) { + case 0: return f0; + case 1: return f1; + case 2: return f2; + case 3: return f3; + case 4: return f4; + } + return data[index - FIELDS_STORE_SIZE]; + } + + private void setImpl(int index, Object value) + { + switch (index) { + case 0: f0 = value; break; + case 1: f1 = value; break; + case 2: f2 = value; break; + case 3: f3 = value; break; + case 4: f4 = value; break; + default: data[index - FIELDS_STORE_SIZE] = value; + } + + } + + public int indexOf(Object obj) + { + int N = size; + for (int i = 0; i != N; ++i) { + Object current = getImpl(i); + if (current == obj || (current != null && current.equals(obj))) { + return i; + } + } + return -1; + } + + public int lastIndexOf(Object obj) + { + for (int i = size; i != 0;) { + --i; + Object current = getImpl(i); + if (current == obj || (current != null && current.equals(obj))) { + return i; + } + } + return -1; + } + + public final Object peek() + { + int N = size; + if (N == 0) throw onEmptyStackTopRead(); + return getImpl(N - 1); + } + + public final Object pop() + { + if (sealed) throw onSeledMutation(); + int N = size; + --N; + Object top; + switch (N) { + case -1: throw onEmptyStackTopRead(); + case 0: top = f0; f0 = null; break; + case 1: top = f1; f1 = null; break; + case 2: top = f2; f2 = null; break; + case 3: top = f3; f3 = null; break; + case 4: top = f4; f4 = null; break; + default: + top = data[N - FIELDS_STORE_SIZE]; + data[N - FIELDS_STORE_SIZE] = null; + } + size = N; + return top; + } + + public final void push(Object value) + { + add(value); + } + + public final void add(Object value) + { + if (sealed) throw onSeledMutation(); + int N = size; + if (N >= FIELDS_STORE_SIZE) { + ensureCapacity(N + 1); + } + size = N + 1; + setImpl(N, value); + } + + public final void add(int index, Object value) + { + int N = size; + if (!(0 <= index && index <= N)) throw onInvalidIndex(index, N + 1); + if (sealed) throw onSeledMutation(); + Object tmp; + switch (index) { + case 0: + if (N == 0) { f0 = value; break; } + tmp = f0; f0 = value; value = tmp; + case 1: + if (N == 1) { f1 = value; break; } + tmp = f1; f1 = value; value = tmp; + case 2: + if (N == 2) { f2 = value; break; } + tmp = f2; f2 = value; value = tmp; + case 3: + if (N == 3) { f3 = value; break; } + tmp = f3; f3 = value; value = tmp; + case 4: + if (N == 4) { f4 = value; break; } + tmp = f4; f4 = value; value = tmp; + + index = FIELDS_STORE_SIZE; + default: + ensureCapacity(N + 1); + if (index != N) { + System.arraycopy(data, index - FIELDS_STORE_SIZE, + data, index - FIELDS_STORE_SIZE + 1, + N - index); + } + data[index - FIELDS_STORE_SIZE] = value; + } + size = N + 1; + } + + public final void remove(int index) + { + int N = size; + if (!(0 <= index && index < N)) throw onInvalidIndex(index, N); + if (sealed) throw onSeledMutation(); + --N; + switch (index) { + case 0: + if (N == 0) { f0 = null; break; } + f0 = f1; + case 1: + if (N == 1) { f1 = null; break; } + f1 = f2; + case 2: + if (N == 2) { f2 = null; break; } + f2 = f3; + case 3: + if (N == 3) { f3 = null; break; } + f3 = f4; + case 4: + if (N == 4) { f4 = null; break; } + f4 = data[0]; + + index = FIELDS_STORE_SIZE; + default: + if (index != N) { + System.arraycopy(data, index - FIELDS_STORE_SIZE + 1, + data, index - FIELDS_STORE_SIZE, + N - index); + } + data[N - FIELDS_STORE_SIZE] = null; + } + size = N; + } + + public final void clear() + { + if (sealed) throw onSeledMutation(); + int N = size; + for (int i = 0; i != N; ++i) { + setImpl(i, null); + } + size = 0; + } + + public final Object[] toArray() + { + Object[] array = new Object[size]; + toArray(array, 0); + return array; + } + + public final void toArray(Object[] array) + { + toArray(array, 0); + } + + public final void toArray(Object[] array, int offset) + { + int N = size; + switch (N) { + default: + System.arraycopy(data, 0, array, offset + FIELDS_STORE_SIZE, + N - FIELDS_STORE_SIZE); + case 5: array[offset + 4] = f4; + case 4: array[offset + 3] = f3; + case 3: array[offset + 2] = f2; + case 2: array[offset + 1] = f1; + case 1: array[offset + 0] = f0; + case 0: break; + } + } + + private void ensureCapacity(int minimalCapacity) + { + int required = minimalCapacity - FIELDS_STORE_SIZE; + if (required <= 0) throw new IllegalArgumentException(); + if (data == null) { + int alloc = FIELDS_STORE_SIZE * 2; + if (alloc < required) { + alloc = required; + } + data = new Object[alloc]; + } else { + int alloc = data.length; + if (alloc < required) { + if (alloc <= FIELDS_STORE_SIZE) { + alloc = FIELDS_STORE_SIZE * 2; + } else { + alloc *= 2; + } + if (alloc < required) { + alloc = required; + } + Object[] tmp = new Object[alloc]; + if (size > FIELDS_STORE_SIZE) { + System.arraycopy(data, 0, tmp, 0, + size - FIELDS_STORE_SIZE); + } + data = tmp; + } + } + } + + private static RuntimeException onInvalidIndex(int index, int upperBound) + { + // \u2209 is "NOT ELEMENT OF" + String msg = index+" \u2209 [0, "+upperBound+')'; + throw new IndexOutOfBoundsException(msg); + } + + private static RuntimeException onEmptyStackTopRead() + { + throw new RuntimeException("Empty stack"); + } + + private static RuntimeException onSeledMutation() + { + throw new IllegalStateException("Attempt to modify sealed array"); + } + + private void writeObject(ObjectOutputStream os) throws IOException + { + os.defaultWriteObject(); + int N = size; + for (int i = 0; i != N; ++i) { + Object obj = getImpl(i); + os.writeObject(obj); + } + } + + private void readObject(ObjectInputStream is) + throws IOException, ClassNotFoundException + { + is.defaultReadObject(); // It reads size + int N = size; + if (N > FIELDS_STORE_SIZE) { + data = new Object[N - FIELDS_STORE_SIZE]; + } + for (int i = 0; i != N; ++i) { + Object obj = is.readObject(); + setImpl(i, obj); + } + } + +// Number of data elements + private int size; + + private boolean sealed; + + private static final int FIELDS_STORE_SIZE = 5; + private transient Object f0, f1, f2, f3, f4; + private transient Object[] data; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjToIntMap.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjToIntMap.java new file mode 100644 index 0000000..4aa7d23 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ObjToIntMap.java @@ -0,0 +1,697 @@ +/* -*- 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): + * Igor Bukanov + * + * 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.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Map to associate objects to integers. + * The map does not synchronize any of its operation, so either use + * it from a single thread or do own synchronization or perform all mutation + * operations on one thread before passing the map to others + * + * @author Igor Bukanov + * + */ + +public class ObjToIntMap implements Serializable +{ + static final long serialVersionUID = -1542220580748809402L; + +// Map implementation via hashtable, +// follows "The Art of Computer Programming" by Donald E. Knuth + +// ObjToIntMap is a copy cat of ObjToIntMap with API adjusted to object keys + + public static class Iterator { + + Iterator(ObjToIntMap master) { + this.master = master; + } + + final void init(Object[] keys, int[] values, int keyCount) { + this.keys = keys; + this.values = values; + this.cursor = -1; + this.remaining = keyCount; + } + + public void start() { + master.initIterator(this); + next(); + } + + public boolean done() { + return remaining < 0; + } + + public void next() { + if (remaining == -1) Kit.codeBug(); + if (remaining == 0) { + remaining = -1; + cursor = -1; + }else { + for (++cursor; ; ++cursor) { + Object key = keys[cursor]; + if (key != null && key != DELETED) { + --remaining; + break; + } + } + } + } + + public Object getKey() { + Object key = keys[cursor]; + if (key == UniqueTag.NULL_VALUE) { key = null; } + return key; + } + + public int getValue() { + return values[cursor]; + } + + public void setValue(int value) { + values[cursor] = value; + } + + ObjToIntMap master; + private int cursor; + private int remaining; + private Object[] keys; + private int[] values; + } + + public ObjToIntMap() { + this(4); + } + + public ObjToIntMap(int keyCountHint) { + if (keyCountHint < 0) Kit.codeBug(); + // Table grow when number of stored keys >= 3/4 of max capacity + int minimalCapacity = keyCountHint * 4 / 3; + int i; + for (i = 2; (1 << i) < minimalCapacity; ++i) { } + power = i; + if (check && power < 2) Kit.codeBug(); + } + + public boolean isEmpty() { + return keyCount == 0; + } + + public int size() { + return keyCount; + } + + public boolean has(Object key) { + if (key == null) { key = UniqueTag.NULL_VALUE; } + return 0 <= findIndex(key); + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key is absent + */ + public int get(Object key, int defaultValue) { + if (key == null) { key = UniqueTag.NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + return defaultValue; + } + + /** + * Get integer value assigned with key. + * @return key integer value + * @throws RuntimeException if key does not exist + */ + public int getExisting(Object key) { + if (key == null) { key = UniqueTag.NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + // Key must exist + Kit.codeBug(); + return 0; + } + + public void put(Object key, int value) { + if (key == null) { key = UniqueTag.NULL_VALUE; } + int index = ensureIndex(key); + values[index] = value; + } + + /** + * If table already contains a key that equals to keyArg, return that key + * while setting its value to zero, otherwise add keyArg with 0 value to + * the table and return it. + */ + public Object intern(Object keyArg) { + boolean nullKey = false; + if (keyArg == null) { + nullKey = true; + keyArg = UniqueTag.NULL_VALUE; + } + int index = ensureIndex(keyArg); + values[index] = 0; + return (nullKey) ? null : keys[index]; + } + + public void remove(Object key) { + if (key == null) { key = UniqueTag.NULL_VALUE; } + int index = findIndex(key); + if (0 <= index) { + keys[index] = DELETED; + --keyCount; + } + } + + public void clear() { + int i = keys.length; + while (i != 0) { + keys[--i] = null; + } + keyCount = 0; + occupiedCount = 0; + } + + public Iterator newIterator() { + return new Iterator(this); + } + + // The sole purpose of the method is to avoid accessing private fields + // from the Iterator inner class to workaround JDK 1.1 compiler bug which + // generates code triggering VerifierError on recent JVMs + final void initIterator(Iterator i) { + i.init(keys, values, keyCount); + } + + /** Return array of present keys */ + public Object[] getKeys() { + Object[] array = new Object[keyCount]; + getKeys(array, 0); + return array; + } + + public void getKeys(Object[] array, int offset) { + int count = keyCount; + for (int i = 0; count != 0; ++i) { + Object key = keys[i]; + if (key != null && key != DELETED) { + if (key == UniqueTag.NULL_VALUE) { key = null; } + array[offset] = key; + ++offset; + --count; + } + } + } + + private static int tableLookupStep(int fraction, int mask, int power) { + int shift = 32 - 2 * power; + if (shift >= 0) { + return ((fraction >>> shift) & mask) | 1; + } + else { + return (fraction & (mask >>> -shift)) | 1; + } + } + + private int findIndex(Object key) { + if (keys != null) { + int hash = key.hashCode(); + int fraction = hash * A; + int index = fraction >>> (32 - power); + Object test = keys[index]; + if (test != null) { + int N = 1 << power; + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + // Search in table after first failed attempt + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + for (;;) { + if (check) { + if (n >= occupiedCount) Kit.codeBug(); + ++n; + } + index = (index + step) & mask; + test = keys[index]; + if (test == null) { + break; + } + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + } + } + } + return -1; + } + +// Insert key that is not present to table without deleted entries +// and enough free space + private int insertNewKey(Object key, int hash) { + if (check && occupiedCount != keyCount) Kit.codeBug(); + if (check && keyCount == 1 << power) Kit.codeBug(); + int fraction = hash * A; + int index = fraction >>> (32 - power); + int N = 1 << power; + if (keys[index] != null) { + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int firstIndex = index; + do { + if (check && keys[index] == DELETED) Kit.codeBug(); + index = (index + step) & mask; + if (check && firstIndex == index) Kit.codeBug(); + } while (keys[index] != null); + } + keys[index] = key; + values[N + index] = hash; + ++occupiedCount; + ++keyCount; + + return index; + } + + private void rehashTable() { + if (keys == null) { + if (check && keyCount != 0) Kit.codeBug(); + if (check && occupiedCount != 0) Kit.codeBug(); + int N = 1 << power; + keys = new Object[N]; + values = new int[2 * N]; + } + else { + // Check if removing deleted entries would free enough space + if (keyCount * 2 >= occupiedCount) { + // Need to grow: less then half of deleted entries + ++power; + } + int N = 1 << power; + Object[] oldKeys = keys; + int[] oldValues = values; + int oldN = oldKeys.length; + keys = new Object[N]; + values = new int[2 * N]; + + int remaining = keyCount; + occupiedCount = keyCount = 0; + for (int i = 0; remaining != 0; ++i) { + Object key = oldKeys[i]; + if (key != null && key != DELETED) { + int keyHash = oldValues[oldN + i]; + int index = insertNewKey(key, keyHash); + values[index] = oldValues[i]; + --remaining; + } + } + } + } + +// Ensure key index creating one if necessary + private int ensureIndex(Object key) { + int hash = key.hashCode(); + int index = -1; + int firstDeleted = -1; + if (keys != null) { + int fraction = hash * A; + index = fraction >>> (32 - power); + Object test = keys[index]; + if (test != null) { + int N = 1 << power; + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + if (test == DELETED) { + firstDeleted = index; + } + + // Search in table after first failed attempt + int mask = N - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + for (;;) { + if (check) { + if (n >= occupiedCount) Kit.codeBug(); + ++n; + } + index = (index + step) & mask; + test = keys[index]; + if (test == null) { + break; + } + if (test == key + || (values[N + index] == hash && test.equals(key))) + { + return index; + } + if (test == DELETED && firstDeleted < 0) { + firstDeleted = index; + } + } + } + } + // Inserting of new key + if (check && keys != null && keys[index] != null) + Kit.codeBug(); + if (firstDeleted >= 0) { + index = firstDeleted; + } + else { + // Need to consume empty entry: check occupation level + if (keys == null || occupiedCount * 4 >= (1 << power) * 3) { + // Too litle unused entries: rehash + rehashTable(); + return insertNewKey(key, hash); + } + ++occupiedCount; + } + keys[index] = key; + values[(1 << power) + index] = hash; + ++keyCount; + return index; + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + + int count = keyCount; + for (int i = 0; count != 0; ++i) { + Object key = keys[i]; + if (key != null && key != DELETED) { + --count; + out.writeObject(key); + out.writeInt(values[i]); + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + int writtenKeyCount = keyCount; + if (writtenKeyCount != 0) { + keyCount = 0; + int N = 1 << power; + keys = new Object[N]; + values = new int[2 * N]; + for (int i = 0; i != writtenKeyCount; ++i) { + Object key = in.readObject(); + int hash = key.hashCode(); + int index = insertNewKey(key, hash); + values[index] = in.readInt(); + } + } + } + +// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32) +// See Knuth etc. + private static final int A = 0x9e3779b9; + + private static final Object DELETED = new Object(); + +// Structure of kyes and values arrays (N == 1 << power): +// keys[0 <= i < N]: key value or null or DELETED mark +// values[0 <= i < N]: value of key at keys[i] +// values[N <= i < 2*N]: hash code of key at keys[i-N] + + private transient Object[] keys; + private transient int[] values; + + private int power; + private int keyCount; + private transient int occupiedCount; // == keyCount + deleted_count + +// If true, enables consitency checks + private static final boolean check = false; + +/* TEST START + + public static void main(String[] args) { + if (!check) { + System.err.println("Set check to true and re-run"); + throw new RuntimeException("Set check to true and re-run"); + } + + ObjToIntMap map; + map = new ObjToIntMap(0); + testHash(map, 3); + map = new ObjToIntMap(0); + testHash(map, 10 * 1000); + map = new ObjToIntMap(); + testHash(map, 10 * 1000); + map = new ObjToIntMap(30 * 1000); + testHash(map, 10 * 100); + map.clear(); + testHash(map, 4); + map = new ObjToIntMap(0); + testHash(map, 10 * 100); + } + + private static void testHash(ObjToIntMap map, int N) { + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(-1 == map.get(key, -1)); + map.put(key, i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + Object[] keys = map.getKeys(); + check(keys.length == N); + for (int i = 0; i != N; ++i) { + Object key = keys[i]; + check(map.has(key)); + } + + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == map.get(key, -1)); + } + + int Nsqrt = -1; + for (int i = 0; ; ++i) { + if (i * i >= N) { + Nsqrt = i; + break; + } + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == 2 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(-1 - i * i); + map.put(key, i); + check(i == map.get(key, -1)); + } + + check(map.size() == 3 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(-1 - i * i); + map.remove(key); + check(!map.has(key)); + } + + check(map.size() == 2 * N - Nsqrt); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + int j = intSqrt(i); + if (j * j == i) { + check(j == map.get(key, -1)); + }else { + check(i == map.get(key, -1)); + } + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i * i); + map.remove(key); + check(-2 == map.get(key, -2)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + map.put(key, i); + check(i == map.get(key, -2)); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == map.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + ObjToIntMap copy = (ObjToIntMap)writeAndRead(map); + check(copy.size() == N); + + for (int i = 0; i != N; ++i) { + Object key = testKey(i); + check(i == copy.get(key, -1)); + } + + System.out.print("."); System.out.flush(); + checkSameMaps(copy, map); + + System.out.println(); System.out.flush(); + } + + private static void checkSameMaps(ObjToIntMap map1, ObjToIntMap map2) { + check(map1.size() == map2.size()); + Object[] keys = map1.getKeys(); + check(keys.length == map1.size()); + for (int i = 0; i != keys.length; ++i) { + check(map1.get(keys[i], -1) == map2.get(keys[i], -1)); + } + } + + private static void check(boolean condition) { + if (!condition) Kit.codeBug(); + } + + private static Object[] testPool; + + private static Object testKey(int i) { + int MAX_POOL = 100; + if (0 <= i && i < MAX_POOL) { + if (testPool != null && testPool[i] != null) { + return testPool[i]; + } + } + Object x = new Double(i + 0.5); + if (0 <= i && i < MAX_POOL) { + if (testPool == null) { + testPool = new Object[MAX_POOL]; + } + testPool[i] = x; + } + return x; + } + + private static int intSqrt(int i) { + int approx = (int)Math.sqrt(i) + 1; + while (approx * approx > i) { + --approx; + } + return approx; + } + + private static Object writeAndRead(Object obj) { + try { + java.io.ByteArrayOutputStream + bos = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream + out = new java.io.ObjectOutputStream(bos); + out.writeObject(obj); + out.close(); + byte[] data = bos.toByteArray(); + java.io.ByteArrayInputStream + bis = new java.io.ByteArrayInputStream(data); + java.io.ObjectInputStream + in = new java.io.ObjectInputStream(bis); + Object result = in.readObject(); + in.close(); + return result; + }catch (Exception ex) { + throw new RuntimeException("Unexpected"); + } + } + +// TEST END */ + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Parser.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Parser.java new file mode 100644 index 0000000..80cb937 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Parser.java @@ -0,0 +1,2554 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Mike Ang + * Igor Bukanov + * Yuh-Ruey Chen + * Ethan Hugg + * Bob Jervis + * Terry Lucas + * Mike McCabe + * Milen Nankov + * Norris Boyd + * + * 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.io.Reader; +import java.io.IOException; +import java.util.Hashtable; + +/** + * This class implements the JavaScript parser. + * + * It is based on the C source files jsparse.c and jsparse.h + * in the jsref package. + * + * @see TokenStream + * + * @author Mike McCabe + * @author Brendan Eich + */ + +public class Parser +{ + // TokenInformation flags : currentFlaggedToken stores them together + // with token type + final static int + CLEAR_TI_MASK = 0xFFFF, // mask to clear token information bits + TI_AFTER_EOL = 1 << 16, // first token of the source line + TI_CHECK_LABEL = 1 << 17; // indicates to check for label + + CompilerEnvirons compilerEnv; + private ErrorReporter errorReporter; + /*APPJET*//*no longer:private*/ String sourceURI; + boolean calledByCompileFunction; + + /*APPJET*//*no longer:private*/ TokenStream ts; + private int currentFlaggedToken; + /*APPJET*//*no longer:private*/ int syntaxErrorCount; + + private IRFactory nf; + + private int nestingOfFunction; + + private Decompiler decompiler; + private String encodedSource; + +// The following are per function variables and should be saved/restored +// during function parsing. +// XXX Move to separated class? + ScriptOrFnNode currentScriptOrFn; + Node.Scope currentScope; + private int nestingOfWith; + private Hashtable labelSet; // map of label names into nodes + private ObjArray loopSet; + private ObjArray loopAndSwitchSet; + private boolean hasReturnValue; + private int endFlags; +// end of per function variables + + /*APPJET*/public int lastConsumedTokenLine = -1; + + public int getCurrentLineNumber() { + return ts.getLineno(); + } + + // Exception to unwind + private static class ParserException extends RuntimeException + { + static final long serialVersionUID = 5882582646773765630L; + } + + public Parser(CompilerEnvirons compilerEnv, ErrorReporter errorReporter) + { + this.compilerEnv = compilerEnv; + this.errorReporter = errorReporter; + } + + protected Decompiler createDecompiler(CompilerEnvirons compilerEnv) + { + return new Decompiler(); + } + + void addStrictWarning(String messageId, String messageArg) + { + if (compilerEnv.isStrictMode()) + addWarning(messageId, messageArg); + } + + void addWarning(String messageId, String messageArg) + { + String message = ScriptRuntime.getMessage1(messageId, messageArg); + if (compilerEnv.reportWarningAsError()) { + ++syntaxErrorCount; + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } else + errorReporter.warning(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + void addError(String messageId) + { + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage0(messageId); + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + void addError(String messageId, String messageArg) + { + ++syntaxErrorCount; + String message = ScriptRuntime.getMessage1(messageId, messageArg); + errorReporter.error(message, sourceURI, ts.getLineno(), + ts.getLine(), ts.getOffset()); + } + + RuntimeException reportError(String messageId) + { + addError(messageId); + + // Throw a ParserException exception to unwind the recursive descent + // parse. + throw new ParserException(); + } + + /*APPJET*//*added method*/ + RuntimeException reportError(String messageId, String messageArg) + { + addError(messageId, messageArg); + + // Throw a ParserException exception to unwind the recursive descent + // parse. + throw new ParserException(); + } + + /*APPJET*//*no longer: private*/int peekToken() + throws IOException + { + int tt = currentFlaggedToken; + if (tt == Token.EOF) { + tt = ts.getToken(); + if (tt == Token.EOL) { + do { + tt = ts.getToken(); + } while (tt == Token.EOL); + tt |= TI_AFTER_EOL; + } + currentFlaggedToken = tt; + } + return tt & CLEAR_TI_MASK; + } + + private int peekFlaggedToken() + throws IOException + { + peekToken(); + return currentFlaggedToken; + } + + /*APPJET*//*no longer:private*/ void consumeToken() + { + currentFlaggedToken = Token.EOF; + /*APPJET*/lastConsumedTokenLine = ts.getLineno(); + } + + private int nextToken() + throws IOException + { + int tt = peekToken(); + consumeToken(); + return tt; + } + + private int nextFlaggedToken() + throws IOException + { + peekToken(); + int ttFlagged = currentFlaggedToken; + consumeToken(); + return ttFlagged; + } + + private boolean matchToken(int toMatch) + throws IOException + { + int tt = peekToken(); + if (tt != toMatch) { + return false; + } + consumeToken(); + return true; + } + + private int peekTokenOrEOL() + throws IOException + { + int tt = peekToken(); + // Check for last peeked token flags + if ((currentFlaggedToken & TI_AFTER_EOL) != 0) { + tt = Token.EOL; + } + return tt; + } + + private void setCheckForLabel() + { + if ((currentFlaggedToken & CLEAR_TI_MASK) != Token.NAME) + throw Kit.codeBug(); + currentFlaggedToken |= TI_CHECK_LABEL; + } + + private void mustMatchToken(int toMatch, String messageId) + throws IOException, ParserException + { + if (!matchToken(toMatch)) { + reportError(messageId); + } + } + + /*APPJET*//*added method*/ + private void mustMatchToken(int toMatch, String messageId, String messageArg) + throws IOException, ParserException + { + if (!matchToken(toMatch)) { + reportError(messageId, messageArg); + } + } + + private void mustHaveXML() + { + if (!compilerEnv.isXmlAvailable()) { + reportError("msg.XML.not.available"); + } + } + + public String getEncodedSource() + { + return encodedSource; + } + + public boolean eof() + { + return ts.eof(); + } + + boolean insideFunction() + { + return nestingOfFunction != 0; + } + + void pushScope(Node node) { + Node.Scope scopeNode = (Node.Scope) node; + if (scopeNode.getParentScope() != null) throw Kit.codeBug(); + scopeNode.setParent(currentScope); + currentScope = scopeNode; + } + + void popScope() { + currentScope = currentScope.getParentScope(); + } + + private Node enterLoop(Node loopLabel, boolean doPushScope) + { + Node loop = nf.createLoopNode(loopLabel, ts.getLineno()); + if (loopSet == null) { + loopSet = new ObjArray(); + if (loopAndSwitchSet == null) { + loopAndSwitchSet = new ObjArray(); + } + } + loopSet.push(loop); + loopAndSwitchSet.push(loop); + if (doPushScope) { + pushScope(loop); + } + return loop; + } + + private void exitLoop(boolean doPopScope) + { + loopSet.pop(); + loopAndSwitchSet.pop(); + if (doPopScope) { + popScope(); + } + } + + private Node enterSwitch(Node switchSelector, int lineno) + { + Node switchNode = nf.createSwitch(switchSelector, lineno); + if (loopAndSwitchSet == null) { + loopAndSwitchSet = new ObjArray(); + } + loopAndSwitchSet.push(switchNode); + return switchNode; + } + + private void exitSwitch() + { + loopAndSwitchSet.pop(); + } + + /* + * Build a parse tree from the given sourceString. + * + * @return an Object representing the parsed + * program. If the parse fails, null will be returned. (The + * parse failure will result in a call to the ErrorReporter from + * CompilerEnvirons.) + */ + public ScriptOrFnNode parse(String sourceString, + String sourceURI, int lineno) + { + this.sourceURI = sourceURI; + this.ts = new TokenStream(this, null, sourceString, lineno); + try { + return parse(); + } catch (IOException ex) { + // Should never happen + throw new IllegalStateException(); + } + } + + /* + * Build a parse tree from the given sourceString. + * + * @return an Object representing the parsed + * program. If the parse fails, null will be returned. (The + * parse failure will result in a call to the ErrorReporter from + * CompilerEnvirons.) + */ + public ScriptOrFnNode parse(Reader sourceReader, + String sourceURI, int lineno) + throws IOException + { + this.sourceURI = sourceURI; + this.ts = new TokenStream(this, sourceReader, null, lineno); + return parse(); + } + + private ScriptOrFnNode parse() + throws IOException + { + this.decompiler = createDecompiler(compilerEnv); + this.nf = new IRFactory(this); + currentScriptOrFn = nf.createScript(); + currentScope = currentScriptOrFn; + int sourceStartOffset = decompiler.getCurrentOffset(); + this.encodedSource = null; + decompiler.addToken(Token.SCRIPT); + + this.currentFlaggedToken = Token.EOF; + this.syntaxErrorCount = 0; + + int baseLineno = ts.getLineno(); // line number where source starts + + /*APPJET*/lastConsumedTokenLine = baseLineno; + + /* so we have something to add nodes to until + * we've collected all the source */ + Node pn = nf.createLeaf(Token.BLOCK); + + try { + for (;;) { + int tt = peekToken(); + + if (tt <= Token.EOF) { + break; + } + + Node n; + if (tt == Token.FUNCTION) { + consumeToken(); + try { + n = function(calledByCompileFunction + ? FunctionNode.FUNCTION_EXPRESSION + : FunctionNode.FUNCTION_STATEMENT); + } catch (ParserException e) { + break; + } + } else { + n = statement(); + } + nf.addChildToBack(pn, n); + } + } catch (StackOverflowError ex) { + String msg = ScriptRuntime.getMessage0( + "msg.too.deep.parser.recursion"); + throw Context.reportRuntimeError(msg, sourceURI, + ts.getLineno(), null, 0); + } + + if (this.syntaxErrorCount != 0) { + String msg = String.valueOf(this.syntaxErrorCount); + msg = ScriptRuntime.getMessage1("msg.got.syntax.errors", msg); + throw errorReporter.runtimeError(msg, sourceURI, baseLineno, + null, 0); + } + + currentScriptOrFn.setSourceName(sourceURI); + currentScriptOrFn.setBaseLineno(baseLineno); + currentScriptOrFn.setEndLineno(ts.getLineno()); + + int sourceEndOffset = decompiler.getCurrentOffset(); + currentScriptOrFn.setEncodedSourceBounds(sourceStartOffset, + sourceEndOffset); + + nf.initScript(currentScriptOrFn, pn); + + if (compilerEnv.isGeneratingSource()) { + encodedSource = decompiler.getEncodedSource(); + } + this.decompiler = null; // It helps GC + + return currentScriptOrFn; + } + + /* + * The C version of this function takes an argument list, + * which doesn't seem to be needed for tree generation... + * it'd only be useful for checking argument hiding, which + * I'm not doing anyway... + */ + private Node parseFunctionBody() + throws IOException + { + ++nestingOfFunction; + Node pn = nf.createBlock(ts.getLineno()); + try { + bodyLoop: for (;;) { + Node n; + int tt = peekToken(); + switch (tt) { + case Token.ERROR: + case Token.EOF: + case Token.RC: + break bodyLoop; + + case Token.FUNCTION: + consumeToken(); + n = function(FunctionNode.FUNCTION_STATEMENT); + break; + default: + n = statement(); + break; + } + nf.addChildToBack(pn, n); + } + } catch (ParserException e) { + // Ignore it + } finally { + --nestingOfFunction; + } + + return pn; + } + + private Node function(int functionType) + throws IOException, ParserException + { + int syntheticType = functionType; + int baseLineno = ts.getLineno(); // line number where source starts + + int functionSourceStart = decompiler.markFunctionStart(functionType); + String name; + Node memberExprNode = null; + if (matchToken(Token.NAME)) { + name = ts.getString(); + decompiler.addName(name); + if (!matchToken(Token.LP)) { + if (compilerEnv.isAllowMemberExprAsFunctionName()) { + // Extension to ECMA: if 'function <name>' does not follow + // by '(', assume <name> starts memberExpr + Node memberExprHead = nf.createName(name); + name = ""; + memberExprNode = memberExprTail(false, memberExprHead); + } + mustMatchToken(Token.LP, "msg.no.paren.parms"); + } + } else if (matchToken(Token.LP)) { + // Anonymous function + name = ""; + } else { + name = ""; + if (compilerEnv.isAllowMemberExprAsFunctionName()) { + // Note that memberExpr can not start with '(' like + // in function (1+2).toString(), because 'function (' already + // processed as anonymous function + memberExprNode = memberExpr(false); + } + mustMatchToken(Token.LP, "msg.no.paren.parms"); + } + + if (memberExprNode != null) { + syntheticType = FunctionNode.FUNCTION_EXPRESSION; + } + + if (syntheticType != FunctionNode.FUNCTION_EXPRESSION && + name.length() > 0) + { + // Function statements define a symbol in the enclosing scope + defineSymbol(Token.FUNCTION, name); + } + + boolean nested = insideFunction(); + + FunctionNode fnNode = nf.createFunction(name); + if (nested || nestingOfWith > 0) { + // 1. Nested functions are not affected by the dynamic scope flag + // as dynamic scope is already a parent of their scope. + // 2. Functions defined under the with statement also immune to + // this setup, in which case dynamic scope is ignored in favor + // of with object. + fnNode.itsIgnoreDynamicScope = true; + } + int functionIndex = currentScriptOrFn.addFunction(fnNode); + + int functionSourceEnd; + + ScriptOrFnNode savedScriptOrFn = currentScriptOrFn; + currentScriptOrFn = fnNode; + Node.Scope savedCurrentScope = currentScope; + currentScope = fnNode; + int savedNestingOfWith = nestingOfWith; + nestingOfWith = 0; + Hashtable savedLabelSet = labelSet; + labelSet = null; + ObjArray savedLoopSet = loopSet; + loopSet = null; + ObjArray savedLoopAndSwitchSet = loopAndSwitchSet; + loopAndSwitchSet = null; + boolean savedHasReturnValue = hasReturnValue; + int savedFunctionEndFlags = endFlags; + + Node destructuring = null; + Node body; + try { + decompiler.addToken(Token.LP); + if (!matchToken(Token.RP)) { + boolean first = true; + do { + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + int tt = peekToken(); + if (tt == Token.LB || tt == Token.LC) { + // Destructuring assignment for parameters: add a + // dummy parameter name, and add a statement to the + // body to initialize variables from the destructuring + // assignment + if (destructuring == null) { + destructuring = new Node(Token.COMMA); + } + String parmName = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LP, parmName); + destructuring.addChildToBack( + nf.createDestructuringAssignment(Token.VAR, + primaryExpr(), nf.createName(parmName))); + } else { + mustMatchToken(Token.NAME, "msg.no.parm"); + String s = ts.getString(); + defineSymbol(Token.LP, s); + decompiler.addName(s); + } + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RP, "msg.no.paren.after.parms"); + } + decompiler.addToken(Token.RP); + + mustMatchToken(Token.LC, "msg.no.brace.body"); + decompiler.addEOL(Token.LC); + body = parseFunctionBody(); + if (destructuring != null) { + body.addChildToFront( + new Node(Token.EXPR_VOID, destructuring, ts.getLineno())); + } + mustMatchToken(Token.RC, "msg.no.brace.after.body"); + + if (compilerEnv.isStrictMode() && !body.hasConsistentReturnUsage()) + { + String msg = name.length() > 0 ? "msg.no.return.value" + : "msg.anon.no.return.value"; + addStrictWarning(msg, name); + } + + if (syntheticType == FunctionNode.FUNCTION_EXPRESSION && + name.length() > 0 && currentScope.getSymbol(name) == null) + { + // Function expressions define a name only in the body of the + // function, and only if not hidden by a parameter name + defineSymbol(Token.FUNCTION, name); + } + + decompiler.addToken(Token.RC); + functionSourceEnd = decompiler.markFunctionEnd(functionSourceStart); + if (functionType != FunctionNode.FUNCTION_EXPRESSION) { + // Add EOL only if function is not part of expression + // since it gets SEMI + EOL from Statement in that case + decompiler.addToken(Token.EOL); + } + } + finally { + hasReturnValue = savedHasReturnValue; + endFlags = savedFunctionEndFlags; + loopAndSwitchSet = savedLoopAndSwitchSet; + loopSet = savedLoopSet; + labelSet = savedLabelSet; + nestingOfWith = savedNestingOfWith; + currentScriptOrFn = savedScriptOrFn; + currentScope = savedCurrentScope; + } + + fnNode.setEncodedSourceBounds(functionSourceStart, functionSourceEnd); + fnNode.setSourceName(sourceURI); + fnNode.setBaseLineno(baseLineno); + fnNode.setEndLineno(ts.getLineno()); + + Node pn = nf.initFunction(fnNode, functionIndex, body, syntheticType); + if (memberExprNode != null) { + pn = nf.createAssignment(Token.ASSIGN, memberExprNode, pn); + if (functionType != FunctionNode.FUNCTION_EXPRESSION) { + // XXX check JScript behavior: should it be createExprStatement? + pn = nf.createExprStatementNoReturn(pn, baseLineno); + } + } + return pn; + } + + private Node statements(Node scope) + throws IOException + { + Node pn = scope != null ? scope : nf.createBlock(ts.getLineno()); + + int tt; + while ((tt = peekToken()) > Token.EOF && tt != Token.RC) { + nf.addChildToBack(pn, statement()); + } + + return pn; + } + + private Node condition() + throws IOException, ParserException + { + mustMatchToken(Token.LP, "msg.no.paren.cond"); + decompiler.addToken(Token.LP); + Node pn = expr(false); + mustMatchToken(Token.RP, "msg.no.paren.after.cond"); + decompiler.addToken(Token.RP); + + // Report strict warning on code like "if (a = 7) ...". Suppress the + // warning if the condition is parenthesized, like "if ((a = 7)) ...". + if (pn.getProp(Node.PARENTHESIZED_PROP) == null && + (pn.getType() == Token.SETNAME || pn.getType() == Token.SETPROP || + pn.getType() == Token.SETELEM)) + { + addStrictWarning("msg.equal.as.assign", ""); + } + return pn; + } + + // match a NAME; return null if no match. + private Node matchJumpLabelName() + throws IOException, ParserException + { + Node label = null; + + int tt = peekTokenOrEOL(); + if (tt == Token.NAME) { + consumeToken(); + String name = ts.getString(); + decompiler.addName(name); + if (labelSet != null) { + label = (Node)labelSet.get(name); + } + if (label == null) { + reportError("msg.undef.label"); + } + } + + return label; + } + + private Node statement() + throws IOException + { + try { + Node pn = statementHelper(null); + if (pn != null) { + if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) + addStrictWarning("msg.no.side.effects", ""); + return pn; + } + } catch (ParserException e) { } + + // skip to end of statement + int lineno = ts.getLineno(); + guessingStatementEnd: for (;;) { + int tt = peekTokenOrEOL(); + consumeToken(); + switch (tt) { + case Token.ERROR: + case Token.EOF: + case Token.EOL: + case Token.SEMI: + break guessingStatementEnd; + } + } + return nf.createExprStatement(nf.createName("error"), lineno); + } + + /*APPJET*/ /*begin*/ + private Node statementHelper(Node statementLabel) + throws IOException, ParserException { + + Node pn = statementHelper0(statementLabel); + if (pn != null && pn.getType() != Token.BLOCK && pn.getType() != Token.LOOP) { + pn.statementEndLineNum = lastConsumedTokenLine; + } + return pn; + } + /*end*/ + + private Node statementHelper0(Node statementLabel) /*APPJET*/ + throws IOException, ParserException + { + Node pn = null; + int tt = peekToken(); + + switch (tt) { + case Token.IF: { + consumeToken(); + + decompiler.addToken(Token.IF); + int lineno = ts.getLineno(); + Node cond = condition(); + /*APPJET*/cond.lineno = lineno; + /*APPJET*/cond.statementEndLineNum = lastConsumedTokenLine; + decompiler.addEOL(Token.LC); + Node ifTrue = statement(); + Node ifFalse = null; + if (matchToken(Token.ELSE)) { + decompiler.addToken(Token.RC); + decompiler.addToken(Token.ELSE); + decompiler.addEOL(Token.LC); + ifFalse = statement(); + } + decompiler.addEOL(Token.RC); + pn = nf.createIf(cond, ifTrue, ifFalse, lineno); + return pn; + } + + case Token.SWITCH: { + consumeToken(); + + decompiler.addToken(Token.SWITCH); + int lineno = ts.getLineno(); + mustMatchToken(Token.LP, "msg.no.paren.switch"); + decompiler.addToken(Token.LP); + /*APPJET*/Node toSwitchOn = expr(false); + /*APPJET*/toSwitchOn.lineno = lineno; + /*APPJET*/toSwitchOn.statementEndLineNum = lastConsumedTokenLine; + pn = enterSwitch(toSwitchOn, lineno); /*APPJET*/ + try { + mustMatchToken(Token.RP, "msg.no.paren.after.switch"); + decompiler.addToken(Token.RP); + mustMatchToken(Token.LC, "msg.no.brace.switch"); + decompiler.addEOL(Token.LC); + + boolean hasDefault = false; + switchLoop: for (;;) { + tt = nextToken(); + Node caseExpression; + switch (tt) { + case Token.RC: + break switchLoop; + + case Token.CASE: + decompiler.addToken(Token.CASE); + caseExpression = expr(false); + mustMatchToken(Token.COLON, "msg.no.colon.case"); + decompiler.addEOL(Token.COLON); + break; + + case Token.DEFAULT: + if (hasDefault) { + reportError("msg.double.switch.default"); + } + decompiler.addToken(Token.DEFAULT); + hasDefault = true; + caseExpression = null; + mustMatchToken(Token.COLON, "msg.no.colon.case"); + decompiler.addEOL(Token.COLON); + break; + + default: + reportError("msg.bad.switch"); + break switchLoop; + } + + Node block = nf.createLeaf(Token.BLOCK); + while ((tt = peekToken()) != Token.RC + && tt != Token.CASE + && tt != Token.DEFAULT + && tt != Token.EOF) + { + nf.addChildToBack(block, statement()); + } + + // caseExpression == null => add default label + nf.addSwitchCase(pn, caseExpression, block); + } + decompiler.addEOL(Token.RC); + nf.closeSwitch(pn); + } finally { + exitSwitch(); + } + return pn; + } + + case Token.WHILE: { + consumeToken(); + decompiler.addToken(Token.WHILE); + + Node loop = enterLoop(statementLabel, true); + try { + /*APPJET*/int lineno = ts.getLineno(); + Node cond = condition(); + /*APPJET*/cond.lineno = lineno; + /*APPJET*/cond.statementEndLineNum = lastConsumedTokenLine; + decompiler.addEOL(Token.LC); + Node body = statement(); + decompiler.addEOL(Token.RC); + pn = nf.createWhile(loop, cond, body); + } finally { + exitLoop(true); + } + return pn; + } + + case Token.DO: { + consumeToken(); + decompiler.addToken(Token.DO); + decompiler.addEOL(Token.LC); + + Node loop = enterLoop(statementLabel, true); + try { + Node body = statement(); + decompiler.addToken(Token.RC); + mustMatchToken(Token.WHILE, "msg.no.while.do"); + decompiler.addToken(Token.WHILE); + Node cond = condition(); + pn = nf.createDoWhile(loop, body, cond); + } finally { + exitLoop(true); + } + // Always auto-insert semicolon to follow SpiderMonkey: + // It is required by ECMAScript but is ignored by the rest of + // world, see bug 238945 + matchToken(Token.SEMI); + decompiler.addEOL(Token.SEMI); + return pn; + } + + case Token.FOR: { + consumeToken(); + boolean isForEach = false; + decompiler.addToken(Token.FOR); + + Node loop = enterLoop(statementLabel, true); + try { + Node init; // Node init is also foo in 'foo in object' + Node cond; // Node cond is also object in 'foo in object' + Node incr = null; + Node body; + int declType = -1; + + // See if this is a for each () instead of just a for () + if (matchToken(Token.NAME)) { + decompiler.addName(ts.getString()); + if (ts.getString().equals("each")) { + isForEach = true; + } else { + reportError("msg.no.paren.for"); + } + } + + mustMatchToken(Token.LP, "msg.no.paren.for"); + decompiler.addToken(Token.LP); + tt = peekToken(); + if (tt == Token.SEMI) { + init = nf.createLeaf(Token.EMPTY); + } else { + if (tt == Token.VAR || tt == Token.LET) { + // set init to a var list or initial + consumeToken(); // consume the token + decompiler.addToken(tt); + init = variables(true, tt); + declType = tt; + } + else { + init = expr(true); + } + } + + if (matchToken(Token.IN)) { + decompiler.addToken(Token.IN); + // 'cond' is the object over which we're iterating + cond = expr(false); + } else { // ordinary for loop + mustMatchToken(Token.SEMI, "msg.no.semi.for"); + decompiler.addToken(Token.SEMI); + if (peekToken() == Token.SEMI) { + // no loop condition + cond = nf.createLeaf(Token.EMPTY); + } else { + cond = expr(false); + } + + mustMatchToken(Token.SEMI, "msg.no.semi.for.cond"); + decompiler.addToken(Token.SEMI); + if (peekToken() == Token.RP) { + incr = nf.createLeaf(Token.EMPTY); + } else { + incr = expr(false); + } + } + + mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"); + decompiler.addToken(Token.RP); + decompiler.addEOL(Token.LC); + /*APPJET*/int parenEndLine = lastConsumedTokenLine; + body = statement(); + decompiler.addEOL(Token.RC); + + if (incr == null) { + // cond could be null if 'in obj' got eaten + // by the init node. + pn = nf.createForIn(declType, loop, init, cond, body, + isForEach); + } else { + pn = nf.createFor(loop, init, cond, incr, body); + } + /*APPJET*/ // use the LOOP object to hold the range of the paren'd expr + /*APPJET*/pn.statementEndLineNum = parenEndLine; + } finally { + exitLoop(true); + } + return pn; + } + + case Token.TRY: { + consumeToken(); + int lineno = ts.getLineno(); + + Node tryblock; + Node catchblocks = null; + Node finallyblock = null; + + decompiler.addToken(Token.TRY); + if (peekToken() != Token.LC) { + reportError("msg.no.brace.try"); + } + decompiler.addEOL(Token.LC); + tryblock = statement(); + decompiler.addEOL(Token.RC); + + catchblocks = nf.createLeaf(Token.BLOCK); + + boolean sawDefaultCatch = false; + int peek = peekToken(); + if (peek == Token.CATCH) { + while (matchToken(Token.CATCH)) { + if (sawDefaultCatch) { + reportError("msg.catch.unreachable"); + } + decompiler.addToken(Token.CATCH); + mustMatchToken(Token.LP, "msg.no.paren.catch"); + decompiler.addToken(Token.LP); + + mustMatchToken(Token.NAME, "msg.bad.catchcond"); + String varName = ts.getString(); + decompiler.addName(varName); + + Node catchCond = null; + if (matchToken(Token.IF)) { + decompiler.addToken(Token.IF); + catchCond = expr(false); + } else { + sawDefaultCatch = true; + } + + mustMatchToken(Token.RP, "msg.bad.catchcond"); + decompiler.addToken(Token.RP); + mustMatchToken(Token.LC, "msg.no.brace.catchblock"); + decompiler.addEOL(Token.LC); + + nf.addChildToBack(catchblocks, + nf.createCatch(varName, catchCond, + statements(null), + ts.getLineno())); + + mustMatchToken(Token.RC, "msg.no.brace.after.body"); + decompiler.addEOL(Token.RC); + } + } else if (peek != Token.FINALLY) { + mustMatchToken(Token.FINALLY, "msg.try.no.catchfinally"); + } + + if (matchToken(Token.FINALLY)) { + decompiler.addToken(Token.FINALLY); + decompiler.addEOL(Token.LC); + finallyblock = statement(); + decompiler.addEOL(Token.RC); + } + + pn = nf.createTryCatchFinally(tryblock, catchblocks, + finallyblock, lineno); + + return pn; + } + + case Token.THROW: { + consumeToken(); + if (peekTokenOrEOL() == Token.EOL) { + // ECMAScript does not allow new lines before throw expression, + // see bug 256617 + reportError("msg.bad.throw.eol"); + } + + int lineno = ts.getLineno(); + decompiler.addToken(Token.THROW); + pn = nf.createThrow(expr(false), lineno); + break; + } + + case Token.BREAK: { + consumeToken(); + int lineno = ts.getLineno(); + + decompiler.addToken(Token.BREAK); + + // matchJumpLabelName only matches if there is one + Node breakStatement = matchJumpLabelName(); + if (breakStatement == null) { + if (loopAndSwitchSet == null || loopAndSwitchSet.size() == 0) { + reportError("msg.bad.break"); + return null; + } + breakStatement = (Node)loopAndSwitchSet.peek(); + } + pn = nf.createBreak(breakStatement, lineno); + break; + } + + case Token.CONTINUE: { + consumeToken(); + int lineno = ts.getLineno(); + + decompiler.addToken(Token.CONTINUE); + + Node loop; + // matchJumpLabelName only matches if there is one + Node label = matchJumpLabelName(); + if (label == null) { + if (loopSet == null || loopSet.size() == 0) { + reportError("msg.continue.outside"); + return null; + } + loop = (Node)loopSet.peek(); + } else { + loop = nf.getLabelLoop(label); + if (loop == null) { + reportError("msg.continue.nonloop"); + return null; + } + } + pn = nf.createContinue(loop, lineno); + break; + } + + case Token.WITH: { + consumeToken(); + + decompiler.addToken(Token.WITH); + int lineno = ts.getLineno(); + mustMatchToken(Token.LP, "msg.no.paren.with"); + decompiler.addToken(Token.LP); + Node obj = expr(false); + /*APPJET*/obj.lineno = lineno; + /*APPJET*/obj.statementEndLineNum = lastConsumedTokenLine; + mustMatchToken(Token.RP, "msg.no.paren.after.with"); + decompiler.addToken(Token.RP); + decompiler.addEOL(Token.LC); + + ++nestingOfWith; + Node body; + try { + body = statement(); + } finally { + --nestingOfWith; + } + + decompiler.addEOL(Token.RC); + + pn = nf.createWith(obj, body, lineno); + return pn; + } + + case Token.CONST: + case Token.VAR: { + consumeToken(); + decompiler.addToken(tt); + pn = variables(false, tt); + break; + } + + case Token.LET: { + consumeToken(); + decompiler.addToken(Token.LET); + if (peekToken() == Token.LP) { + pn = let(true); + } else { + pn = variables(false, tt); + } + return pn; + } + + case Token.RETURN: + case Token.YIELD: { + pn = returnOrYield(tt, false); + break; + } + + case Token.DEBUGGER: + consumeToken(); + decompiler.addToken(Token.DEBUGGER); + pn = nf.createDebugger(ts.getLineno()); + break; + + case Token.LC: + consumeToken(); + if (statementLabel != null) { + decompiler.addToken(Token.LC); + } + Node scope = nf.createScopeNode(Token.BLOCK, ts.getLineno()); + pushScope(scope); + try { + statements(scope); + mustMatchToken(Token.RC, "msg.no.brace.block"); + if (statementLabel != null) { + decompiler.addEOL(Token.RC); + } + return scope; + } finally { + popScope(); + } + + case Token.ERROR: + // Fall thru, to have a node for error recovery to work on + case Token.SEMI: + consumeToken(); + pn = nf.createLeaf(Token.EMPTY); + return pn; + + case Token.FUNCTION: { + consumeToken(); + pn = function(FunctionNode.FUNCTION_EXPRESSION_STATEMENT); + return pn; + } + + case Token.DEFAULT : + consumeToken(); + mustHaveXML(); + + decompiler.addToken(Token.DEFAULT); + int nsLine = ts.getLineno(); + + if (!(matchToken(Token.NAME) + && ts.getString().equals("xml"))) + { + reportError("msg.bad.namespace"); + } + decompiler.addName(" xml"); + + if (!(matchToken(Token.NAME) + && ts.getString().equals("namespace"))) + { + reportError("msg.bad.namespace"); + } + decompiler.addName(" namespace"); + + if (!matchToken(Token.ASSIGN)) { + reportError("msg.bad.namespace"); + } + decompiler.addToken(Token.ASSIGN); + + Node expr = expr(false); + pn = nf.createDefaultNamespace(expr, nsLine); + break; + + case Token.NAME: { + int lineno = ts.getLineno(); + String name = ts.getString(); + setCheckForLabel(); + pn = expr(false); + if (pn.getType() != Token.LABEL) { + pn = nf.createExprStatement(pn, lineno); + } else { + // Parsed the label: push back token should be + // colon that primaryExpr left untouched. + if (peekToken() != Token.COLON) Kit.codeBug(); + consumeToken(); + // depend on decompiling lookahead to guess that that + // last name was a label. + decompiler.addName(name); + decompiler.addEOL(Token.COLON); + + if (labelSet == null) { + labelSet = new Hashtable(); + } else if (labelSet.containsKey(name)) { + reportError("msg.dup.label"); + } + + boolean firstLabel; + if (statementLabel == null) { + firstLabel = true; + statementLabel = pn; + } else { + // Discard multiple label nodes and use only + // the first: it allows to simplify IRFactory + firstLabel = false; + } + labelSet.put(name, statementLabel); + try { + pn = statementHelper(statementLabel); + } finally { + labelSet.remove(name); + } + if (firstLabel) { + pn = nf.createLabeledStatement(statementLabel, pn); + } + return pn; + } + break; + } + + default: { + int lineno = ts.getLineno(); + pn = expr(false); + pn = nf.createExprStatement(pn, lineno); + break; + } + } + + int ttFlagged = peekFlaggedToken(); + switch (ttFlagged & CLEAR_TI_MASK) { + case Token.SEMI: + // Consume ';' as a part of expression + consumeToken(); + break; + case Token.ERROR: + case Token.EOF: + case Token.RC: + // Autoinsert ; + break; + default: + if ((ttFlagged & TI_AFTER_EOL) == 0) { + // Report error if no EOL or autoinsert ; otherwise + reportError("msg.no.semi.stmt"); + } + break; + } + decompiler.addEOL(Token.SEMI); + + return pn; + } + + /** + * Returns whether or not the bits in the mask have changed to all set. + * @param before bits before change + * @param after bits after change + * @param mask mask for bits + * @return true if all the bits in the mask are set in "after" but not + * "before" + */ + private static final boolean nowAllSet(int before, int after, int mask) + { + return ((before & mask) != mask) && ((after & mask) == mask); + } + + private Node returnOrYield(int tt, boolean exprContext) + throws IOException, ParserException + { + if (!insideFunction()) { + reportError(tt == Token.RETURN ? "msg.bad.return" + : "msg.bad.yield"); + } + consumeToken(); + decompiler.addToken(tt); + int lineno = ts.getLineno(); + + Node e; + /* This is ugly, but we don't want to require a semicolon. */ + switch (peekTokenOrEOL()) { + case Token.SEMI: + case Token.RC: + case Token.EOF: + case Token.EOL: + case Token.ERROR: + case Token.RB: + case Token.RP: + case Token.YIELD: + e = null; + break; + default: + e = expr(false); + break; + } + + int before = endFlags; + Node ret; + + if (tt == Token.RETURN) { + if (e == null ) { + endFlags |= Node.END_RETURNS; + } else { + endFlags |= Node.END_RETURNS_VALUE; + hasReturnValue = true; + } + ret = nf.createReturn(e, lineno); + + // see if we need a strict mode warning + if (nowAllSet(before, endFlags, + Node.END_RETURNS|Node.END_RETURNS_VALUE)) + { + addStrictWarning("msg.return.inconsistent", ""); + } + } else { + endFlags |= Node.END_YIELDS; + ret = nf.createYield(e, lineno); + if (!exprContext) + ret = new Node(Token.EXPR_VOID, ret, lineno); + } + + // see if we are mixing yields and value returns. + if (nowAllSet(before, endFlags, + Node.END_YIELDS|Node.END_RETURNS_VALUE)) + { + String name = ((FunctionNode)currentScriptOrFn).getFunctionName(); + if (name.length() == 0) + addError("msg.anon.generator.returns", ""); + else + addError("msg.generator.returns", name); + } + + return ret; + } + + /** + * Parse a 'var' or 'const' statement, or a 'var' init list in a for + * statement. + * @param inFor true if we are currently in the midst of the init + * clause of a for. + * @param inStatement true if called in a statement (as opposed to an + * expression) context + * @param declType A token value: either VAR, CONST, or LET depending on + * context. + * @return The parsed statement + * @throws IOException + * @throws ParserException + */ + private Node variables(boolean inFor, int declType) + throws IOException, ParserException + { + Node result = nf.createVariables(declType, ts.getLineno()); + boolean first = true; + for (;;) { + Node destructuring = null; + String s = null; + int tt = peekToken(); + if (tt == Token.LB || tt == Token.LC) { + // Destructuring assignment, e.g., var [a,b] = ... + destructuring = primaryExpr(); + } else { + // Simple variable name + mustMatchToken(Token.NAME, "msg.bad.var", + Token.name(declType).toLowerCase()); + s = ts.getString(); + + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + + decompiler.addName(s); + defineSymbol(declType, s); + } + + Node init = null; + if (matchToken(Token.ASSIGN)) { + decompiler.addToken(Token.ASSIGN); + init = assignExpr(inFor); + } + + if (destructuring != null) { + if (init == null) { + if (!inFor) + reportError("msg.destruct.assign.no.init"); + nf.addChildToBack(result, destructuring); + } else { + nf.addChildToBack(result, + nf.createDestructuringAssignment(declType, + destructuring, init)); + } + } else { + Node name = nf.createName(s); + if (init != null) + nf.addChildToBack(name, init); + nf.addChildToBack(result, name); + } + + if (!matchToken(Token.COMMA)) + break; + } + return result; + } + + + private Node let(boolean isStatement) + throws IOException, ParserException + { + mustMatchToken(Token.LP, "msg.no.paren.after.let"); + decompiler.addToken(Token.LP); + Node result = nf.createScopeNode(Token.LET, ts.getLineno()); + pushScope(result); + try { + Node vars = variables(false, Token.LET); + nf.addChildToBack(result, vars); + mustMatchToken(Token.RP, "msg.no.paren.let"); + decompiler.addToken(Token.RP); + if (isStatement && peekToken() == Token.LC) { + // let statement + consumeToken(); + decompiler.addEOL(Token.LC); + nf.addChildToBack(result, statements(null)); + mustMatchToken(Token.RC, "msg.no.curly.let"); + decompiler.addToken(Token.RC); + } else { + // let expression + result.setType(Token.LETEXPR); + nf.addChildToBack(result, expr(false)); + if (isStatement) { + // let expression in statement context + result = nf.createExprStatement(result, ts.getLineno()); + } + } + } finally { + popScope(); + } + return result; + } + + void defineSymbol(int declType, String name) { + Node.Scope definingScope = currentScope.getDefiningScope(name); + Node.Scope.Symbol symbol = definingScope != null + ? definingScope.getSymbol(name) + : null; + boolean error = false; + if (symbol != null && (symbol.declType == Token.CONST || + declType == Token.CONST)) + { + error = true; + } else { + switch (declType) { + case Token.LET: + if (symbol != null && definingScope == currentScope) { + error = symbol.declType == Token.LET; + } + currentScope.putSymbol(name, + new Node.Scope.Symbol(declType, name)); + break; + + case Token.VAR: + case Token.CONST: + case Token.FUNCTION: + if (symbol != null) { + if (symbol.declType == Token.VAR) + addStrictWarning("msg.var.redecl", name); + else if (symbol.declType == Token.LP) { + addStrictWarning("msg.var.hides.arg", name); + } + } else { + currentScriptOrFn.putSymbol(name, + new Node.Scope.Symbol(declType, name)); + } + break; + + case Token.LP: + if (symbol != null) { + // must be duplicate parameter. Second parameter hides the + // first, so go ahead and add the second pararameter + addWarning("msg.dup.parms", name); + } + currentScriptOrFn.putSymbol(name, + new Node.Scope.Symbol(declType, name)); + break; + + default: + throw Kit.codeBug(); + } + } + if (error) { + addError(symbol.declType == Token.CONST ? "msg.const.redecl" : + symbol.declType == Token.LET ? "msg.let.redecl" : + symbol.declType == Token.VAR ? "msg.var.redecl" : + symbol.declType == Token.FUNCTION ? "msg.fn.redecl" : + "msg.parm.redecl", name); + } + } + + private Node expr(boolean inForInit) + throws IOException, ParserException + { + Node pn = assignExpr(inForInit); + while (matchToken(Token.COMMA)) { + decompiler.addToken(Token.COMMA); + if (compilerEnv.isStrictMode() && !pn.hasSideEffects()) + addStrictWarning("msg.no.side.effects", ""); + if (peekToken() == Token.YIELD) { + reportError("msg.yield.parenthesized"); + } + pn = nf.createBinary(Token.COMMA, pn, assignExpr(inForInit)); + } + return pn; + } + + private Node assignExpr(boolean inForInit) + throws IOException, ParserException + { + int tt = peekToken(); + if (tt == Token.YIELD) { + consumeToken(); + return returnOrYield(tt, true); + } + Node pn = condExpr(inForInit); + + tt = peekToken(); + if (Token.FIRST_ASSIGN <= tt && tt <= Token.LAST_ASSIGN) { + consumeToken(); + decompiler.addToken(tt); + pn = nf.createAssignment(tt, pn, assignExpr(inForInit)); + } + + return pn; + } + + private Node condExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = orExpr(inForInit); + + if (matchToken(Token.HOOK)) { + decompiler.addToken(Token.HOOK); + Node ifTrue = assignExpr(false); + mustMatchToken(Token.COLON, "msg.no.colon.cond"); + decompiler.addToken(Token.COLON); + Node ifFalse = assignExpr(inForInit); + return nf.createCondExpr(pn, ifTrue, ifFalse); + } + + return pn; + } + + private Node orExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = andExpr(inForInit); + if (matchToken(Token.OR)) { + decompiler.addToken(Token.OR); + pn = nf.createBinary(Token.OR, pn, orExpr(inForInit)); + } + + return pn; + } + + private Node andExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitOrExpr(inForInit); + if (matchToken(Token.AND)) { + decompiler.addToken(Token.AND); + pn = nf.createBinary(Token.AND, pn, andExpr(inForInit)); + } + + return pn; + } + + private Node bitOrExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitXorExpr(inForInit); + while (matchToken(Token.BITOR)) { + decompiler.addToken(Token.BITOR); + pn = nf.createBinary(Token.BITOR, pn, bitXorExpr(inForInit)); + } + return pn; + } + + private Node bitXorExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = bitAndExpr(inForInit); + while (matchToken(Token.BITXOR)) { + decompiler.addToken(Token.BITXOR); + pn = nf.createBinary(Token.BITXOR, pn, bitAndExpr(inForInit)); + } + return pn; + } + + private Node bitAndExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = eqExpr(inForInit); + while (matchToken(Token.BITAND)) { + decompiler.addToken(Token.BITAND); + pn = nf.createBinary(Token.BITAND, pn, eqExpr(inForInit)); + } + return pn; + } + + private Node eqExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = relExpr(inForInit); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: + consumeToken(); + int decompilerToken = tt; + int parseToken = tt; + if (compilerEnv.getLanguageVersion() == Context.VERSION_1_2) { + // JavaScript 1.2 uses shallow equality for == and != . + // In addition, convert === and !== for decompiler into + // == and != since the decompiler is supposed to show + // canonical source and in 1.2 ===, !== are allowed + // only as an alias to ==, !=. + switch (tt) { + case Token.EQ: + parseToken = Token.SHEQ; + break; + case Token.NE: + parseToken = Token.SHNE; + break; + case Token.SHEQ: + decompilerToken = Token.EQ; + break; + case Token.SHNE: + decompilerToken = Token.NE; + break; + } + } + decompiler.addToken(decompilerToken); + pn = nf.createBinary(parseToken, pn, relExpr(inForInit)); + continue; + } + break; + } + return pn; + } + + private Node relExpr(boolean inForInit) + throws IOException, ParserException + { + Node pn = shiftExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.IN: + if (inForInit) + break; + // fall through + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, shiftExpr()); + continue; + } + break; + } + return pn; + } + + private Node shiftExpr() + throws IOException, ParserException + { + Node pn = addExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.LSH: + case Token.URSH: + case Token.RSH: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, addExpr()); + continue; + } + break; + } + return pn; + } + + private Node addExpr() + throws IOException, ParserException + { + Node pn = mulExpr(); + for (;;) { + int tt = peekToken(); + if (tt == Token.ADD || tt == Token.SUB) { + consumeToken(); + decompiler.addToken(tt); + // flushNewLines + pn = nf.createBinary(tt, pn, mulExpr()); + continue; + } + break; + } + + return pn; + } + + private Node mulExpr() + throws IOException, ParserException + { + Node pn = unaryExpr(); + for (;;) { + int tt = peekToken(); + switch (tt) { + case Token.MUL: + case Token.DIV: + case Token.MOD: + consumeToken(); + decompiler.addToken(tt); + pn = nf.createBinary(tt, pn, unaryExpr()); + continue; + } + break; + } + + return pn; + } + + private Node unaryExpr() + throws IOException, ParserException + { + int tt; + + tt = peekToken(); + + switch(tt) { + case Token.VOID: + case Token.NOT: + case Token.BITNOT: + case Token.TYPEOF: + consumeToken(); + decompiler.addToken(tt); + return nf.createUnary(tt, unaryExpr()); + + case Token.ADD: + consumeToken(); + // Convert to special POS token in decompiler and parse tree + decompiler.addToken(Token.POS); + return nf.createUnary(Token.POS, unaryExpr()); + + case Token.SUB: + consumeToken(); + // Convert to special NEG token in decompiler and parse tree + decompiler.addToken(Token.NEG); + return nf.createUnary(Token.NEG, unaryExpr()); + + case Token.INC: + case Token.DEC: + consumeToken(); + decompiler.addToken(tt); + return nf.createIncDec(tt, false, memberExpr(true)); + + case Token.DELPROP: + consumeToken(); + decompiler.addToken(Token.DELPROP); + return nf.createUnary(Token.DELPROP, unaryExpr()); + + case Token.ERROR: + consumeToken(); + break; + + // XML stream encountered in expression. + case Token.LT: + if (compilerEnv.isXmlAvailable()) { + consumeToken(); + Node pn = xmlInitializer(); + return memberExprTail(true, pn); + } + // Fall thru to the default handling of RELOP + + default: + Node pn = memberExpr(true); + + // Don't look across a newline boundary for a postfix incop. + tt = peekTokenOrEOL(); + if (tt == Token.INC || tt == Token.DEC) { + consumeToken(); + decompiler.addToken(tt); + return nf.createIncDec(tt, true, pn); + } + return pn; + } + return nf.createName("error"); // Only reached on error.Try to continue. + + } + + private Node xmlInitializer() throws IOException + { + int tt = ts.getFirstXMLToken(); + if (tt != Token.XML && tt != Token.XMLEND) { + reportError("msg.syntax"); + return null; + } + + /* Make a NEW node to append to. */ + Node pnXML = nf.createLeaf(Token.NEW); + + String xml = ts.getString(); + boolean fAnonymous = xml.trim().startsWith("<>"); + + Node pn = nf.createName(fAnonymous ? "XMLList" : "XML"); + nf.addChildToBack(pnXML, pn); + + pn = null; + Node expr; + for (;;tt = ts.getNextXMLToken()) { + switch (tt) { + case Token.XML: + xml = ts.getString(); + decompiler.addName(xml); + mustMatchToken(Token.LC, "msg.syntax"); + decompiler.addToken(Token.LC); + expr = (peekToken() == Token.RC) + ? nf.createString("") + : expr(false); + mustMatchToken(Token.RC, "msg.syntax"); + decompiler.addToken(Token.RC); + if (pn == null) { + pn = nf.createString(xml); + } else { + pn = nf.createBinary(Token.ADD, pn, nf.createString(xml)); + } + if (ts.isXMLAttribute()) { + /* Need to put the result in double quotes */ + expr = nf.createUnary(Token.ESCXMLATTR, expr); + Node prepend = nf.createBinary(Token.ADD, + nf.createString("\""), + expr); + expr = nf.createBinary(Token.ADD, + prepend, + nf.createString("\"")); + } else { + expr = nf.createUnary(Token.ESCXMLTEXT, expr); + } + pn = nf.createBinary(Token.ADD, pn, expr); + break; + case Token.XMLEND: + xml = ts.getString(); + decompiler.addName(xml); + if (pn == null) { + pn = nf.createString(xml); + } else { + pn = nf.createBinary(Token.ADD, pn, nf.createString(xml)); + } + + nf.addChildToBack(pnXML, pn); + return pnXML; + default: + reportError("msg.syntax"); + return null; + } + } + } + + private void argumentList(Node listNode) + throws IOException, ParserException + { + boolean matched; + matched = matchToken(Token.RP); + if (!matched) { + boolean first = true; + do { + if (!first) + decompiler.addToken(Token.COMMA); + first = false; + if (peekToken() == Token.YIELD) { + reportError("msg.yield.parenthesized"); + } + nf.addChildToBack(listNode, assignExpr(false)); + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RP, "msg.no.paren.arg"); + } + decompiler.addToken(Token.RP); + } + + private Node memberExpr(boolean allowCallSyntax) + throws IOException, ParserException + { + int tt; + + Node pn; + + /* Check for new expressions. */ + tt = peekToken(); + if (tt == Token.NEW) { + /* Eat the NEW token. */ + consumeToken(); + decompiler.addToken(Token.NEW); + + /* Make a NEW node to append to. */ + pn = nf.createCallOrNew(Token.NEW, memberExpr(false)); + + if (matchToken(Token.LP)) { + decompiler.addToken(Token.LP); + /* Add the arguments to pn, if any are supplied. */ + argumentList(pn); + } + + /* XXX there's a check in the C source against + * "too many constructor arguments" - how many + * do we claim to support? + */ + + /* Experimental syntax: allow an object literal to follow a new expression, + * which will mean a kind of anonymous class built with the JavaAdapter. + * the object literal will be passed as an additional argument to the constructor. + */ + tt = peekToken(); + if (tt == Token.LC) { + nf.addChildToBack(pn, primaryExpr()); + } + } else { + pn = primaryExpr(); + } + + return memberExprTail(allowCallSyntax, pn); + } + + private Node memberExprTail(boolean allowCallSyntax, Node pn) + throws IOException, ParserException + { + tailLoop: + for (;;) { + int tt = peekToken(); + switch (tt) { + + case Token.DOT: + case Token.DOTDOT: + { + int memberTypeFlags; + String s; + + consumeToken(); + decompiler.addToken(tt); + memberTypeFlags = 0; + if (tt == Token.DOTDOT) { + mustHaveXML(); + memberTypeFlags = Node.DESCENDANTS_FLAG; + } + if (!compilerEnv.isXmlAvailable()) { + mustMatchToken(Token.NAME, "msg.no.name.after.dot"); + s = ts.getString(); + decompiler.addName(s); + pn = nf.createPropertyGet(pn, null, s, memberTypeFlags); + break; + } + + tt = nextToken(); + switch (tt) { + + // needed for generator.throw(); + case Token.THROW: + decompiler.addName("throw"); + pn = propertyName(pn, "throw", memberTypeFlags); + break; + + // handles: name, ns::name, ns::*, ns::[expr] + case Token.NAME: + s = ts.getString(); + decompiler.addName(s); + pn = propertyName(pn, s, memberTypeFlags); + break; + + // handles: *, *::name, *::*, *::[expr] + case Token.MUL: + decompiler.addName("*"); + pn = propertyName(pn, "*", memberTypeFlags); + break; + + // handles: '@attr', '@ns::attr', '@ns::*', '@ns::*', + // '@::attr', '@::*', '@*', '@*::attr', '@*::*' + case Token.XMLATTR: + decompiler.addToken(Token.XMLATTR); + pn = attributeAccess(pn, memberTypeFlags); + break; + + default: + reportError("msg.no.name.after.dot"); + } + } + break; + + case Token.DOTQUERY: + consumeToken(); + mustHaveXML(); + decompiler.addToken(Token.DOTQUERY); + pn = nf.createDotQuery(pn, expr(false), ts.getLineno()); + mustMatchToken(Token.RP, "msg.no.paren"); + decompiler.addToken(Token.RP); + break; + + case Token.LB: + consumeToken(); + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, null, expr(false), 0); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + break; + + case Token.LP: + if (!allowCallSyntax) { + break tailLoop; + } + consumeToken(); + decompiler.addToken(Token.LP); + pn = nf.createCallOrNew(Token.CALL, pn); + /* Add the arguments to pn, if any are supplied. */ + argumentList(pn); + break; + + default: + break tailLoop; + } + } + return pn; + } + + /* + * Xml attribute expression: + * '@attr', '@ns::attr', '@ns::*', '@ns::*', '@*', '@*::attr', '@*::*' + */ + private Node attributeAccess(Node pn, int memberTypeFlags) + throws IOException + { + memberTypeFlags |= Node.ATTRIBUTE_FLAG; + int tt = nextToken(); + + switch (tt) { + // handles: @name, @ns::name, @ns::*, @ns::[expr] + case Token.NAME: + { + String s = ts.getString(); + decompiler.addName(s); + pn = propertyName(pn, s, memberTypeFlags); + } + break; + + // handles: @*, @*::name, @*::*, @*::[expr] + case Token.MUL: + decompiler.addName("*"); + pn = propertyName(pn, "*", memberTypeFlags); + break; + + // handles @[expr] + case Token.LB: + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, null, expr(false), memberTypeFlags); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + break; + + default: + reportError("msg.no.name.after.xmlAttr"); + pn = nf.createPropertyGet(pn, null, "?", memberTypeFlags); + break; + } + + return pn; + } + + /** + * Check if :: follows name in which case it becomes qualified name + */ + private Node propertyName(Node pn, String name, int memberTypeFlags) + throws IOException, ParserException + { + String namespace = null; + if (matchToken(Token.COLONCOLON)) { + decompiler.addToken(Token.COLONCOLON); + namespace = name; + + int tt = nextToken(); + switch (tt) { + // handles name::name + case Token.NAME: + name = ts.getString(); + decompiler.addName(name); + break; + + // handles name::* + case Token.MUL: + decompiler.addName("*"); + name = "*"; + break; + + // handles name::[expr] + case Token.LB: + decompiler.addToken(Token.LB); + pn = nf.createElementGet(pn, namespace, expr(false), + memberTypeFlags); + mustMatchToken(Token.RB, "msg.no.bracket.index"); + decompiler.addToken(Token.RB); + return pn; + + default: + reportError("msg.no.name.after.coloncolon"); + name = "?"; + } + } + + pn = nf.createPropertyGet(pn, namespace, name, memberTypeFlags); + return pn; + } + + private Node arrayComprehension(String arrayName, Node expr) + throws IOException, ParserException + { + if (nextToken() != Token.FOR) + throw Kit.codeBug(); // shouldn't be here if next token isn't 'for' + decompiler.addName(" "); // space after array literal expr + decompiler.addToken(Token.FOR); + boolean isForEach = false; + if (matchToken(Token.NAME)) { + decompiler.addName(ts.getString()); + if (ts.getString().equals("each")) { + isForEach = true; + } else { + reportError("msg.no.paren.for"); + } + } + mustMatchToken(Token.LP, "msg.no.paren.for"); + decompiler.addToken(Token.LP); + String name; + int tt = peekToken(); + if (tt == Token.LB || tt == Token.LC) { + // handle destructuring assignment + name = currentScriptOrFn.getNextTempName(); + defineSymbol(Token.LP, name); + expr = nf.createBinary(Token.COMMA, + nf.createAssignment(Token.ASSIGN, primaryExpr(), + nf.createName(name)), + expr); + } else if (tt == Token.NAME) { + consumeToken(); + name = ts.getString(); + decompiler.addName(name); + } else { + reportError("msg.bad.var"); + return nf.createNumber(0); + } + + Node init = nf.createName(name); + // Define as a let since we want the scope of the variable to + // be restricted to the array comprehension + defineSymbol(Token.LET, name); + + mustMatchToken(Token.IN, "msg.in.after.for.name"); + decompiler.addToken(Token.IN); + Node iterator = expr(false); + mustMatchToken(Token.RP, "msg.no.paren.for.ctrl"); + decompiler.addToken(Token.RP); + + Node body; + tt = peekToken(); + if (tt == Token.FOR) { + body = arrayComprehension(arrayName, expr); + } else { + Node call = nf.createCallOrNew(Token.CALL, + nf.createPropertyGet(nf.createName(arrayName), null, + "push", 0)); + call.addChildToBack(expr); + body = new Node(Token.EXPR_VOID, call, ts.getLineno()); + if (tt == Token.IF) { + consumeToken(); + decompiler.addToken(Token.IF); + int lineno = ts.getLineno(); + Node cond = condition(); + body = nf.createIf(cond, body, null, lineno); + } + mustMatchToken(Token.RB, "msg.no.bracket.arg"); + decompiler.addToken(Token.RB); + } + + Node loop = enterLoop(null, true); + try { + return nf.createForIn(Token.LET, loop, init, iterator, body, + isForEach); + } finally { + exitLoop(false); + } + } + + private Node primaryExpr() + throws IOException, ParserException + { + Node pn; + + int ttFlagged = nextFlaggedToken(); + int tt = ttFlagged & CLEAR_TI_MASK; + + switch(tt) { + + case Token.FUNCTION: + return function(FunctionNode.FUNCTION_EXPRESSION); + + case Token.LB: { + ObjArray elems = new ObjArray(); + int skipCount = 0; + int destructuringLen = 0; + decompiler.addToken(Token.LB); + boolean after_lb_or_comma = true; + for (;;) { + tt = peekToken(); + + if (tt == Token.COMMA) { + consumeToken(); + decompiler.addToken(Token.COMMA); + if (!after_lb_or_comma) { + after_lb_or_comma = true; + } else { + elems.add(null); + ++skipCount; + } + } else if (tt == Token.RB) { + consumeToken(); + decompiler.addToken(Token.RB); + // for ([a,] in obj) is legal, but for ([a] in obj) is + // not since we have both key and value supplied. The + // trick is that [a,] and [a] are equivalent in other + // array literal contexts. So we calculate a special + // length value just for destructuring assignment. + destructuringLen = elems.size() + + (after_lb_or_comma ? 1 : 0); + break; + } else if (skipCount == 0 && elems.size() == 1 && + tt == Token.FOR) + { + Node scopeNode = nf.createScopeNode(Token.ARRAYCOMP, + ts.getLineno()); + String tempName = currentScriptOrFn.getNextTempName(); + pushScope(scopeNode); + try { + defineSymbol(Token.LET, tempName); + Node expr = (Node) elems.get(0); + Node block = nf.createBlock(ts.getLineno()); + Node init = new Node(Token.EXPR_VOID, + nf.createAssignment(Token.ASSIGN, + nf.createName(tempName), + nf.createCallOrNew(Token.NEW, + nf.createName("Array"))), ts.getLineno()); + block.addChildToBack(init); + block.addChildToBack(arrayComprehension(tempName, + expr)); + scopeNode.addChildToBack(block); + scopeNode.addChildToBack(nf.createName(tempName)); + return scopeNode; + } finally { + popScope(); + } + } else { + if (!after_lb_or_comma) { + reportError("msg.no.bracket.arg"); + } + elems.add(assignExpr(false)); + after_lb_or_comma = false; + } + } + return nf.createArrayLiteral(elems, skipCount, destructuringLen); + } + + case Token.LC: { + ObjArray elems = new ObjArray(); + decompiler.addToken(Token.LC); + if (!matchToken(Token.RC)) { + + boolean first = true; + commaloop: + do { + Object property; + + if (!first) + decompiler.addToken(Token.COMMA); + else + first = false; + + tt = peekToken(); + switch(tt) { + case Token.NAME: + case Token.STRING: + consumeToken(); + // map NAMEs to STRINGs in object literal context + // but tell the decompiler the proper type + String s = ts.getString(); + if (tt == Token.NAME) { + if (s.equals("get") && + peekToken() == Token.NAME) { + decompiler.addToken(Token.GET); + consumeToken(); + s = ts.getString(); + decompiler.addName(s); + property = ScriptRuntime.getIndexObject(s); + if (!getterSetterProperty(elems, property, + true)) + break commaloop; + break; + } else if (s.equals("set") && + peekToken() == Token.NAME) { + decompiler.addToken(Token.SET); + consumeToken(); + s = ts.getString(); + decompiler.addName(s); + property = ScriptRuntime.getIndexObject(s); + if (!getterSetterProperty(elems, property, + false)) + break commaloop; + break; + } + decompiler.addName(s); + } else { + decompiler.addString(s); + } + property = ScriptRuntime.getIndexObject(s); + plainProperty(elems, property); + break; + + case Token.NUMBER: + consumeToken(); + double n = ts.getNumber(); + decompiler.addNumber(n); + property = ScriptRuntime.getIndexObject(n); + plainProperty(elems, property); + break; + + case Token.RC: + // trailing comma is OK. + break commaloop; + default: + reportError("msg.bad.prop"); + break commaloop; + } + } while (matchToken(Token.COMMA)); + + mustMatchToken(Token.RC, "msg.no.brace.prop"); + } + decompiler.addToken(Token.RC); + return nf.createObjectLiteral(elems); + } + + case Token.LET: + decompiler.addToken(Token.LET); + return let(false); + + case Token.LP: + + /* Brendan's IR-jsparse.c makes a new node tagged with + * TOK_LP here... I'm not sure I understand why. Isn't + * the grouping already implicit in the structure of the + * parse tree? also TOK_LP is already overloaded (I + * think) in the C IR as 'function call.' */ + decompiler.addToken(Token.LP); + pn = expr(false); + pn.putProp(Node.PARENTHESIZED_PROP, Boolean.TRUE); + decompiler.addToken(Token.RP); + mustMatchToken(Token.RP, "msg.no.paren"); + return pn; + + case Token.XMLATTR: + mustHaveXML(); + decompiler.addToken(Token.XMLATTR); + pn = attributeAccess(null, 0); + return pn; + + case Token.NAME: { + String name = ts.getString(); + if ((ttFlagged & TI_CHECK_LABEL) != 0) { + if (peekToken() == Token.COLON) { + // Do not consume colon, it is used as unwind indicator + // to return to statementHelper. + // XXX Better way? + return nf.createLabel(ts.getLineno()); + } + } + + decompiler.addName(name); + if (compilerEnv.isXmlAvailable()) { + pn = propertyName(null, name, 0); + } else { + pn = nf.createName(name); + } + return pn; + } + + case Token.NUMBER: { + double n = ts.getNumber(); + decompiler.addNumber(n); + return nf.createNumber(n); + } + + case Token.STRING: { + String s = ts.getString(); + decompiler.addString(s); + return nf.createString(s); + } + + case Token.DIV: + case Token.ASSIGN_DIV: { + // Got / or /= which should be treated as regexp in fact + ts.readRegExp(tt); + String flags = ts.regExpFlags; + ts.regExpFlags = null; + String re = ts.getString(); + decompiler.addRegexp(re, flags); + /*APPJET*/ + int index = currentScriptOrFn.addRegexp + (re, flags, getCurrentLineNumber()); + return nf.createRegExp(index); + } + + case Token.NULL: + case Token.THIS: + case Token.FALSE: + case Token.TRUE: + decompiler.addToken(tt); + return nf.createLeaf(tt); + + case Token.RESERVED: + reportError("msg.reserved.id"); + break; + + case Token.ERROR: + /* the scanner or one of its subroutines reported the error. */ + break; + + case Token.EOF: + reportError("msg.unexpected.eof"); + break; + + default: + reportError("msg.syntax"); + break; + } + return null; // should never reach here + } + + private void plainProperty(ObjArray elems, Object property) + throws IOException { + mustMatchToken(Token.COLON, "msg.no.colon.prop"); + + // OBJLIT is used as ':' in object literal for + // decompilation to solve spacing ambiguity. + decompiler.addToken(Token.OBJECTLIT); + elems.add(property); + elems.add(assignExpr(false)); + } + + private boolean getterSetterProperty(ObjArray elems, Object property, + boolean isGetter) throws IOException { + Node f = function(FunctionNode.FUNCTION_EXPRESSION); + if (f.getType() != Token.FUNCTION) { + reportError("msg.bad.prop"); + return false; + } + int fnIndex = f.getExistingIntProp(Node.FUNCTION_PROP); + FunctionNode fn = currentScriptOrFn.getFunctionNode(fnIndex); + if (fn.getFunctionName().length() != 0) { + reportError("msg.bad.prop"); + return false; + } + elems.add(property); + if (isGetter) { + elems.add(nf.createUnary(Token.GET, f)); + } else { + elems.add(nf.createUnary(Token.SET, f)); + } + return true; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/PolicySecurityController.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/PolicySecurityController.java new file mode 100644 index 0000000..c4d3d7e --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/PolicySecurityController.java @@ -0,0 +1,223 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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.ref.SoftReference; +import java.lang.reflect.UndeclaredThrowableException; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.Policy; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.SecureClassLoader; +import java.util.Map; +import java.util.WeakHashMap; + +import org.mozilla.classfile.ByteCode; +import org.mozilla.classfile.ClassFileWriter; + +/** + * A security controller relying on Java {@link Policy} in effect. When you use + * this security controller, your securityDomain objects must be instances of + * {@link CodeSource} representing the location from where you load your + * scripts. Any Java policy "grant" statements matching the URL and certificate + * in code sources will apply to the scripts. If you specify any certificates + * within your {@link CodeSource} objects, it is your responsibility to verify + * (or not) that the script source files are signed in whatever + * implementation-specific way you're using. + * @author Attila Szegedi + */ +public class PolicySecurityController extends SecurityController +{ + private static final byte[] secureCallerImplBytecode = loadBytecode(); + + // We're storing a CodeSource -> (ClassLoader -> SecureRenderer), since we + // need to have one renderer per class loader. We're using weak hash maps + // and soft references all the way, since we don't want to interfere with + // cleanup of either CodeSource or ClassLoader objects. + private static final Map callers = new WeakHashMap(); + + public Class getStaticSecurityDomainClassInternal() { + return CodeSource.class; + } + + private static class Loader extends SecureClassLoader + implements GeneratedClassLoader + { + private final CodeSource codeSource; + + Loader(ClassLoader parent, CodeSource codeSource) + { + super(parent); + this.codeSource = codeSource; + } + + public Class defineClass(String name, byte[] data) + { + return defineClass(name, data, 0, data.length, codeSource); + } + + public void linkClass(Class cl) + { + resolveClass(cl); + } + } + + public GeneratedClassLoader createClassLoader(final ClassLoader parent, + final Object securityDomain) + { + return (Loader)AccessController.doPrivileged( + new PrivilegedAction() + { + public Object run() + { + return new Loader(parent, (CodeSource)securityDomain); + } + }); + } + + public Object getDynamicSecurityDomain(Object securityDomain) + { + // No separate notion of dynamic security domain - just return what was + // passed in. + return securityDomain; + } + + public Object callWithDomain(final Object securityDomain, final Context cx, + Callable callable, Scriptable scope, Scriptable thisObj, + Object[] args) + { + // Run in doPrivileged as we might be checked for "getClassLoader" + // runtime permission + final ClassLoader classLoader = (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return cx.getApplicationClassLoader(); + } + }); + final CodeSource codeSource = (CodeSource)securityDomain; + Map classLoaderMap; + synchronized(callers) + { + classLoaderMap = (Map)callers.get(codeSource); + if(classLoaderMap == null) + { + classLoaderMap = new WeakHashMap(); + callers.put(codeSource, classLoaderMap); + } + } + SecureCaller caller; + synchronized(classLoaderMap) + { + SoftReference ref = (SoftReference)classLoaderMap.get(classLoader); + if(ref != null) + { + caller = (SecureCaller)ref.get(); + } + else + { + caller = null; + } + if(caller == null) + { + try + { + // Run in doPrivileged as we'll be checked for + // "createClassLoader" runtime permission + caller = (SecureCaller)AccessController.doPrivileged( + new PrivilegedExceptionAction() + { + public Object run() throws Exception + { + Loader loader = new Loader(classLoader, + codeSource); + Class c = loader.defineClass( + SecureCaller.class.getName() + "Impl", + secureCallerImplBytecode); + return c.newInstance(); + } + }); + classLoaderMap.put(classLoader, new SoftReference(caller)); + } + catch(PrivilegedActionException ex) + { + throw new UndeclaredThrowableException(ex.getCause()); + } + } + } + return caller.call(callable, cx, scope, thisObj, args); + } + + public abstract static class SecureCaller + { + public abstract Object call(Callable callable, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args); + } + + + private static byte[] loadBytecode() + { + String secureCallerClassName = SecureCaller.class.getName(); + ClassFileWriter cfw = new ClassFileWriter( + secureCallerClassName + "Impl", secureCallerClassName, + "<generated>"); + cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC); + cfw.addALoad(0); + cfw.addInvoke(ByteCode.INVOKESPECIAL, secureCallerClassName, + "<init>", "()V"); + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)1); + String callableCallSig = + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;"; + + cfw.startMethod("call", + "(Lorg/mozilla/javascript/Callable;" + callableCallSig, + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + for(int i = 1; i < 6; ++i) { + cfw.addALoad(i); + } + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Callable", "call", + "(" + callableCallSig); + cfw.add(ByteCode.ARETURN); + cfw.stopMethod((short)6); + return cfw.toByteArray(); + } +}
\ No newline at end of file diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Ref.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Ref.java new file mode 100644 index 0000000..1e237bc --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Ref.java @@ -0,0 +1,64 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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.io.Serializable; + +/** + * Generic notion of reference object that know how to query/modify the + * target objects based on some property/index. + */ +public abstract class Ref implements Serializable +{ + public boolean has(Context cx) + { + return true; + } + + public abstract Object get(Context cx); + + public abstract Object set(Context cx, Object value); + + public boolean delete(Context cx) + { + return false; + } + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RefCallable.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RefCallable.java new file mode 100644 index 0000000..6d4b61c --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RefCallable.java @@ -0,0 +1,59 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@mir2.org + * + * 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; + +/** + * Object that can allows assignments to the result of function calls. + */ +public interface RefCallable extends Callable +{ + /** + * Perform function call in reference context. + * 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, + * for example, to implement {@link Ref} methods, then store args.clone(), + * not args array itself. + * + * @param cx the current Context for this thread + * @param thisObj the JavaScript <code>this</code> object + * @param args the array of arguments + */ + public Ref refCall(Context cx, Scriptable thisObj, Object[] args); +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RegExpProxy.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RegExpProxy.java new file mode 100644 index 0000000..ac29c6e --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RegExpProxy.java @@ -0,0 +1,71 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * 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; + +/** + * A proxy for the regexp package, so that the regexp package can be + * loaded optionally. + * + * @author Norris Boyd + */ +public interface RegExpProxy +{ + // Types of regexp actions + + public static final int RA_MATCH = 1; + public static final int RA_REPLACE = 2; + public static final int RA_SEARCH = 3; + + public boolean isRegExp(Scriptable obj); + + public Object compileRegExp(Context cx, String source, String flags); + + public Scriptable wrapRegExp(Context cx, Scriptable scope, + Object compiled); + + public Object action(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args, + int actionType); + + public int find_split(Context cx, Scriptable scope, String target, + String separator, Scriptable re, + int[] ip, int[] matchlen, + boolean[] matched, String[][] parensp); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RhinoException.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RhinoException.java new file mode 100644 index 0000000..b7f4a4d --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/RhinoException.java @@ -0,0 +1,306 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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.io.CharArrayWriter; +import java.io.File; +import java.io.FilenameFilter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.util.List; +import java.util.ArrayList; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public abstract class RhinoException extends RuntimeException +{ + RhinoException() + { + Evaluator e = Context.createInterpreter(); + if (e != null) + e.captureStackInfo(this); + } + + RhinoException(String details) + { + super(details); + Evaluator e = Context.createInterpreter(); + if (e != null) + e.captureStackInfo(this); + } + + public final String getMessage() + { + String details = details(); + if (sourceName == null || lineNumber <= 0) { + return details; + } + StringBuffer buf = new StringBuffer(details); + buf.append(" ("); + if (sourceName != null) { + buf.append(sourceName); + } + if (lineNumber > 0) { + buf.append('#'); + buf.append(lineNumber); + } + buf.append(')'); + return buf.toString(); + } + + public String details() + { + return super.getMessage(); + } + + /** + * Get the uri of the script source containing the error, or null + * if that information is not available. + */ + public final String sourceName() + { + return sourceName; + } + + /** + * Initialize the uri of the script source containing the error. + * + * @param sourceName the uri of the script source responsible for the error. + * It should not be <tt>null</tt>. + * + * @throws IllegalStateException if the method is called more then once. + */ + public final void initSourceName(String sourceName) + { + if (sourceName == null) throw new IllegalArgumentException(); + if (this.sourceName != null) throw new IllegalStateException(); + this.sourceName = sourceName; + } + + /** + * Returns the line number of the statement causing the error, + * or zero if not available. + */ + public final int lineNumber() + { + return lineNumber; + } + + /** + * Initialize the line number of the script statement causing the error. + * + * @param lineNumber the line number in the script source. + * It should be positive number. + * + * @throws IllegalStateException if the method is called more then once. + */ + public final void initLineNumber(int lineNumber) + { + if (lineNumber <= 0) throw new IllegalArgumentException(String.valueOf(lineNumber)); + if (this.lineNumber > 0) throw new IllegalStateException(); + this.lineNumber = lineNumber; + } + + /** + * The column number of the location of the error, or zero if unknown. + */ + public final int columnNumber() + { + return columnNumber; + } + + /** + * Initialize the column number of the script statement causing the error. + * + * @param columnNumber the column number in the script source. + * It should be positive number. + * + * @throws IllegalStateException if the method is called more then once. + */ + public final void initColumnNumber(int columnNumber) + { + if (columnNumber <= 0) throw new IllegalArgumentException(String.valueOf(columnNumber)); + if (this.columnNumber > 0) throw new IllegalStateException(); + this.columnNumber = columnNumber; + } + + /** + * The source text of the line causing the error, or null if unknown. + */ + public final String lineSource() + { + return lineSource; + } + + /** + * Initialize the text of the source line containing the error. + * + * @param lineSource the text of the source line responsible for the error. + * It should not be <tt>null</tt>. + * + * @throws IllegalStateException if the method is called more then once. + */ + public final void initLineSource(String lineSource) + { + if (lineSource == null) throw new IllegalArgumentException(); + if (this.lineSource != null) throw new IllegalStateException(); + this.lineSource = lineSource; + } + + final void recordErrorOrigin(String sourceName, int lineNumber, + String lineSource, int columnNumber) + { + // XXX: for compatibility allow for now -1 to mean 0 + if (lineNumber == -1) { + lineNumber = 0; + } + + if (sourceName != null) { + initSourceName(sourceName); + } + if (lineNumber != 0) { + initLineNumber(lineNumber); + } + if (lineSource != null) { + initLineSource(lineSource); + } + if (columnNumber != 0) { + initColumnNumber(columnNumber); + } + } + + private String generateStackTrace() + { + // Get stable reference to work properly with concurrent access + CharArrayWriter writer = new CharArrayWriter(); + super.printStackTrace(new PrintWriter(writer)); + String origStackTrace = writer.toString(); + Evaluator e = Context.createInterpreter(); + if (e != null) + return e.getPatchedStack(this, origStackTrace); + return null; + } + + /** + * Get a string representing the script stack of this exception. + * If optimization is enabled, this corresponds to all java stack elements + * with a source name ending with ".js". + * @return a script stack dump + * @since 1.6R6 + */ + public String getScriptStackTrace() + { + return getScriptStackTrace(new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".js"); + } + }); + } + + /** + * Get a string representing the script stack of this exception. + * If optimization is enabled, this corresponds to all java stack elements + * with a source name matching the <code>filter</code>. + * @param filter the file name filter to determine whether a file is a + * script file + * @return a script stack dump + * @since 1.6R6 + */ + public String getScriptStackTrace(FilenameFilter filter) + { + List interpreterStack; + Evaluator interpreter = Context.createInterpreter(); + if (interpreter != null) + interpreterStack = interpreter.getScriptStack(this); + else + interpreterStack = new ArrayList(); + int interpreterStackIndex = 0; + StringBuffer buffer = new StringBuffer(); + String lineSeparator = SecurityUtilities.getSystemProperty("line.separator"); + StackTraceElement[] stack = getStackTrace(); + for (int i = 0; i < stack.length; i++) { + StackTraceElement e = stack[i]; + String name = e.getFileName(); + if (e.getLineNumber() > -1 && name != null && + filter.accept(null, name)) + { + buffer.append("\tat "); + buffer.append(e.getFileName()); + buffer.append(':'); + buffer.append(e.getLineNumber()); + buffer.append(lineSeparator); + } else if (interpreterStack != null && + "org.mozilla.javascript.Interpreter".equals(e.getClassName()) && + "interpretLoop".equals(e.getMethodName())) + { + buffer.append(interpreterStack.get(interpreterStackIndex++)); + } + } + return buffer.toString(); + } + + public void printStackTrace(PrintWriter s) + { + if (interpreterStackInfo == null) { + super.printStackTrace(s); + } else { + s.print(generateStackTrace()); + } + } + + public void printStackTrace(PrintStream s) + { + if (interpreterStackInfo == null) { + super.printStackTrace(s); + } else { + s.print(generateStackTrace()); + } + } + + private String sourceName; + private int lineNumber; + private String lineSource; + private int columnNumber; + + Object interpreterStackInfo; + int[] interpreterLineData; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Script.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Script.java new file mode 100644 index 0000000..4721ead --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Script.java @@ -0,0 +1,73 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * All compiled scripts implement this interface. + * <p> + * This class encapsulates script execution relative to an + * object scope. + * @since 1.3 + * @author Norris Boyd + */ + +public interface Script { + + /** + * Execute the script. + * <p> + * The script is executed in a particular runtime Context, which + * must be associated with the current thread. + * The script is executed relative to a scope--definitions and + * uses of global top-level variables and functions will access + * properties of the scope object. For compliant ECMA + * programs, the scope must be an object that has been initialized + * as a global object using <code>Context.initStandardObjects</code>. + * <p> + * + * @param cx the Context associated with the current thread + * @param scope the scope to execute relative to + * @return the result of executing the script + * @see org.mozilla.javascript.Context#initStandardObjects() + */ + public Object exec(Context cx, Scriptable scope); + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptOrFnNode.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptOrFnNode.java new file mode 100644 index 0000000..9ea6d1f --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptOrFnNode.java @@ -0,0 +1,241 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * Bob Jervis + * Norris Boyd + * + * 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.util.ArrayList; + +public class ScriptOrFnNode extends Node.Scope { + + public ScriptOrFnNode(int nodeType) { + super(nodeType); + symbols = new ArrayList(4); + setParent(null); + } + + public final String getSourceName() { return sourceName; } + + public final void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public final int getEncodedSourceStart() { return encodedSourceStart; } + + public final int getEncodedSourceEnd() { return encodedSourceEnd; } + + public final void setEncodedSourceBounds(int start, int end) { + this.encodedSourceStart = start; + this.encodedSourceEnd = end; + } + + public final int getBaseLineno() { return this.lineno; } + + public final void setBaseLineno(int lineno) { + // One time action + if (lineno < 0 || this.lineno >= 0) Kit.codeBug(); + this.lineno = lineno; + } + + public final int getEndLineno() { return endLineno; } + + public final void setEndLineno(int lineno) { + // One time action + if (lineno < 0 || endLineno >= 0) Kit.codeBug(); + endLineno = lineno; + } + + public final int getFunctionCount() { + if (functions == null) { return 0; } + return functions.size(); + } + + public final FunctionNode getFunctionNode(int i) { + return (FunctionNode)functions.get(i); + } + + public final int addFunction(FunctionNode fnNode) { + if (fnNode == null) Kit.codeBug(); + if (functions == null) { functions = new ObjArray(); } + functions.add(fnNode); + return functions.size() - 1; + } + + public final int getRegexpCount() { + if (regexps == null) { return 0; } + return regexps.size() / 2; + } + + public final String getRegexpString(int index) { + return (String)regexps.get(index * 2); + } + + public final String getRegexpFlags(int index) { + return (String)regexps.get(index * 2 + 1); + } + + /*APPJET*/public final int getRegexpLineno(int index) { + return (Integer)regexpLinenos.get(index); + } + + public final int addRegexp(String string, String flags, /*APPJET*/ + int lineno) { + if (string == null) Kit.codeBug(); + if (regexps == null) { + /*APPJET*/ + regexps = new ObjArray(); + regexpLinenos = new ObjArray(); + } + regexps.add(string); + regexps.add(flags); + /*APPJET*/regexpLinenos.add(lineno); + return regexps.size() / 2 - 1; + } + + public int getIndexForNameNode(Node nameNode) { + if (variableNames == null) throw Kit.codeBug(); + Node.Scope node = nameNode.getScope(); + Symbol symbol = node == null ? null + : node.getSymbol(nameNode.getString()); + if (symbol == null) + return -1; + return symbol.index; + } + + public final String getParamOrVarName(int index) { + if (variableNames == null) throw Kit.codeBug(); + return variableNames[index]; + } + + public final int getParamCount() { + return paramCount; + } + + public final int getParamAndVarCount() { + if (variableNames == null) throw Kit.codeBug(); + return symbols.size(); + } + + public final String[] getParamAndVarNames() { + if (variableNames == null) throw Kit.codeBug(); + return variableNames; + } + + public final boolean[] getParamAndVarConst() { + if (variableNames == null) throw Kit.codeBug(); + return isConsts; + } + + void addSymbol(Symbol symbol) { + if (variableNames != null) throw Kit.codeBug(); + if (symbol.declType == Token.LP) { + paramCount++; + } + symbols.add(symbol); + } + + /** + * Assign every symbol a unique integer index. Generate arrays of variable + * names and constness that can be indexed by those indices. + * + * @param flattenAllTables if true, flatten all symbol tables, included + * nested block scope symbol tables. If false, just flatten the script's + * or function's symbol table. + */ + void flattenSymbolTable(boolean flattenAllTables) { + if (!flattenAllTables) { + ArrayList newSymbols = new ArrayList(); + if (this.symbolTable != null) { + // Just replace "symbols" with the symbols in this object's + // symbol table. Can't just work from symbolTable map since + // we need to retain duplicate parameters. + for (int i=0; i < symbols.size(); i++) { + Symbol symbol = (Symbol) symbols.get(i); + if (symbol.containingTable == this) { + newSymbols.add(symbol); + } + } + } + symbols = newSymbols; + } + variableNames = new String[symbols.size()]; + isConsts = new boolean[symbols.size()]; + for (int i=0; i < symbols.size(); i++) { + Symbol symbol = (Symbol) symbols.get(i); + variableNames[i] = symbol.name; + isConsts[i] = symbol.declType == Token.CONST; + symbol.index = i; + } + } + + public final Object getCompilerData() + { + return compilerData; + } + + public final void setCompilerData(Object data) + { + if (data == null) throw new IllegalArgumentException(); + // Can only call once + if (compilerData != null) throw new IllegalStateException(); + compilerData = data; + } + + public String getNextTempName() + { + return "$" + tempNumber++; + } + + private int encodedSourceStart; + private int encodedSourceEnd; + private String sourceName; + private int endLineno = -1; + + private ObjArray functions; + private ObjArray regexps; + /*APPJET*/ private ObjArray regexpLinenos; + + private ArrayList symbols; + private int paramCount = 0; + private String[] variableNames; + private boolean[] isConsts; + + private Object compilerData; + private int tempNumber = 0; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptRuntime.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptRuntime.java new file mode 100644 index 0000000..f879581 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptRuntime.java @@ -0,0 +1,3830 @@ +/* -*- 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.io.Serializable; +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; + +/** + * 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 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 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", + "javax", "org.mozilla.javascript.NativeJavaTopPackage", + "org", "org.mozilla.javascript.NativeJavaTopPackage", + "com", "org.mozilla.javascript.NativeJavaTopPackage", + "edu", "org.mozilla.javascript.NativeJavaTopPackage", + "net", "org.mozilla.javascript.NativeJavaTopPackage", + "getClass", "org.mozilla.javascript.NativeJavaTopPackage", + "JavaAdapter", "org.mozilla.javascript.JavaAdapter", + "JavaImporter", "org.mozilla.javascript.ImporterTopLevel", + "Continuation", "org.mozilla.javascript.continuations.Continuation", + // 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); + 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); + + NativeWith.init(scope, sealed); + NativeCall.init(scope, sealed); + NativeScript.init(scope, sealed); + + NativeIterator.init(scope, sealed); // Also initializes NativeGenerator + + 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); + } + + 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 (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) + 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 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; + // 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); + } + + 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.getVeryTopLevelScope(scope); // APPJET + 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) { + // TODO: Change XMLObject to just use Scriptable interface + // to avoid paying cost of instanceof check on *every property + // lookup* ! + 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; + } + + public static Object getObjectPropNoWarn(Object obj, String property, + Context cx) + { + Scriptable sobj = toObjectOrNull(cx, obj); + if (sobj == null) { + throw undefReadError(obj, property); + } + if (obj instanceof XMLObject) { + // TODO: fix as mentioned in note in method above + getObjectProp(sobj, property, cx); + } + 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. + */ + 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 implements Serializable + { + private static final long serialVersionUID = 1L; + Scriptable obj; + Object[] ids; + int index; + ObjToIntMap used; + Object currentId; + int enumType; /* one of ENUM_INIT_KEYS, ENUM_INIT_VALUES, + ENUM_INIT_ARRAY */ + + // 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) + { + /*APPJET 1.6*//* + if (ScriptableObject.hasProperty(obj, + NativeIterator.ITERATOR_PROPERTY_NAME)) + { + Object v = ScriptableObject.getProperty(obj, + NativeIterator.ITERATOR_PROPERTY_NAME); + if (!(v instanceof Callable)) { + throw typeError0("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 typeError0("msg.iterator.primitive"); + } + return (Scriptable) v; + }*/ + return null; + } + + // for backwards compatibility with generated class files + 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 Object enumInit(Object value, Context cx, int enumType) + { + 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 + 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, true); + } + 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; + } + + public static void setEnumNumbers(Object enumObj, boolean enumNumbers) { + ((IdEnumeration)enumObj).enumNumbers = enumNumbers; + } + + public static Boolean enumNext(Object enumObj) + { + IdEnumeration x = (IdEnumeration)enumObj; + if (x.iterator != null) { + Object v = ScriptableObject.getProperty(x.iterator, "next"); + if (!(v instanceof Callable)) + return Boolean.FALSE; + Callable f = (Callable) v; + Context cx = Context.enter(); + 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; + } finally { + Context.exit(); + } + } + 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 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 ? (Object) (new Integer(intId)) + : String.valueOf(intId); + } + 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(x.obj.getParentScope(), elements); + default: + throw Kit.codeBug(); + } + } + + public static Object enumValue(Object enumObj, Context cx) { + IdEnumeration x = (IdEnumeration)enumObj; + + 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 required 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(thisObj, 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 = getCallable(thisObj); + + 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 + callArgs = L <= 1 ? ScriptRuntime.emptyArgs : + getApplyArguments(cx, args[1]); + } 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); + } + + static Object[] getApplyArguments(Context cx, Object arg1) + { + if (arg1 == null || arg1 == Undefined.instance) { + return ScriptRuntime.emptyArgs; + } else if (arg1 instanceof NativeArray || arg1 instanceof Arguments) { + return cx.getElements((Scriptable) arg1); + } else { + throw ScriptRuntime.typeError0("msg.arg.isnt.array"); + } + } + + static Callable getCallable(Scriptable thisObj) + { + 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; + } + return function; + } + + /** + * 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()); + + Evaluator evaluator = Context.createInterpreter(); + if (evaluator == null) { + throw new JavaScriptException("Interpreter not present", + filename, lineNumber); + } + + // Compile with explicit interpreter instance to force interpreter + // mode. + Script script = cx.compileString((String)x, evaluator, + reporter, sourceName, 1, null); + evaluator.setEvalScriptFlag(script); + 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 ScriptableObject && + ((ScriptableObject)value).avoidObjectDetection()) + { + return "undefined"; + } + 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)); + } + + public static String add(String val1, Object val2) { + return val1.concat(toString(val2)); + } + + public static String add(Object val1, String val2) { + return toString(val1).concat(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) { + // See bug 413838. Effectively an extension to ECMA for + // the LiveConnect case. + Object unwrappedX = ((Wrapper)x).unwrap(); + Object unwrappedY = ((Wrapper)y).unwrap(); + return unwrappedX == unwrappedY || + (isPrimitive(unwrappedX) && + isPrimitive(unwrappedY) && + eq(unwrappedX, unwrappedY)); + } + 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; + } + } + + private static boolean isPrimitive(Object obj) { + return (obj instanceof Number) || (obj instanceof String) || + (obj instanceof Boolean); + } + + 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 addInstructionCount(Context cx, int instructionsToAdd) + { + cx.instructionCount += instructionsToAdd; + if (cx.instructionCount > cx.instructionThreshold) + { + cx.observeInstructionCount(cx.instructionCount); + cx.instructionCount = 0; + } + } + + 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 if (cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) { + // With FEATURE_ENHANCED_JAVA_ACCESS, scripts can catch + // all exception types + re = new WrappedException(t); + errorName = "JavaException"; + errorMsg = t.toString(); + } 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.) + /*APPJET NOJAVA*/ + /*catchScopeObject.defineProperty( + "__exception__", Context.javaToJS(t, scope), + 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.getVeryTopLevelScope(scope); // APPJET + 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[] skipIndices, + Context cx, Scriptable scope) + { + final int SKIP_DENSITY = 2; + int count = objects.length; + int skipCount = 0; + if (skipIndices != null) { + skipCount = skipIndices.length; + } + int length = count + skipCount; + if (length > 1 && skipCount * SKIP_DENSITY < length) { + // If not too sparse, create whole array for constructor + Object[] sparse; + if (skipCount == 0) { + sparse = objects; + } else { + sparse = new Object[length]; + int skip = 0; + for (int i = 0, j = 0; i != length; ++i) { + if (skip != skipCount && skipIndices[skip] == i) { + sparse[i] = Scriptable.NOT_FOUND; + ++skip; + continue; + } + sparse[i] = objects[j]; + ++j; + } + } + return cx.newObject(scope, "Array", sparse); + } + + Scriptable arrayObj = cx.newObject(scope, "Array", + ScriptRuntime.emptyArgs); + int skip = 0; + for (int i = 0, j = 0; i != length; ++i) { + if (skip != skipCount && skipIndices[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. + */ + 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, + int lineNumberDelta) + { + int[] linep = new int[1]; + String filename = Context.getSourcePositionFromStack(linep); + if (linep[0] != 0) { + linep[0] += lineNumberDelta; + } + 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) + { + // 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, typeof(value)); + } + + public static RuntimeException notFunctionError(Object obj, Object value, + String propertyName) + { + // Use obj and value for better error reporting + String objString = toString(obj); + if (value == Scriptable.NOT_FOUND) { + return typeError2("msg.function.not.found.in", propertyName, + objString); + } + return typeError3("msg.isnt.function.in", propertyName, objString, + typeof(value)); + } + + private static RuntimeException notXmlError(Object value) + { + throw typeError1("msg.isnt.xml.object", 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]; + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Scriptable.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Scriptable.java new file mode 100644 index 0000000..74e5ba7 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Scriptable.java @@ -0,0 +1,342 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all objects in JavaScript must implement. + * The interface provides for the management of properties and for + * performing conversions. + * <p> + * Host system implementors may find it easier to extend the ScriptableObject + * class rather than implementing Scriptable when writing host objects. + * <p> + * There are many static methods defined in ScriptableObject that perform + * the multiple calls to the Scriptable interface needed in order to + * manipulate properties in prototype chains. + * <p> + * + * @see org.mozilla.javascript.ScriptableObject + * @author Norris Boyd + * @author Nick Thompson + * @author Brendan Eich + */ + +public interface Scriptable { + + /** + * Get the name of the set of objects implemented by this Java class. + * This corresponds to the [[Class]] operation in ECMA and is used + * by Object.prototype.toString() in ECMA.<p> + * See ECMA 8.6.2 and 15.2.4.2. + */ + public String getClassName(); + + /** + * Value returned from <code>get</code> if the property is not + * found. + */ + public static final Object NOT_FOUND = UniqueTag.NOT_FOUND; + + /** + * Get a named property from the object. + * + * Looks property up in this object and returns the associated value + * if found. Returns NOT_FOUND if not found. + * Note that this method is not expected to traverse the prototype + * chain. This is different from the ECMA [[Get]] operation. + * + * Depending on the property selector, the runtime will call + * this method or the form of <code>get</code> that takes an + * integer: + * <table> + * <tr><th>JavaScript code</th><th>Java code</th></tr> + * <tr><td>a.b </td><td>a.get("b", a)</td></tr> + * <tr><td>a["foo"] </td><td>a.get("foo", a)</td></tr> + * <tr><td>a[3] </td><td>a.get(3, a)</td></tr> + * <tr><td>a["3"] </td><td>a.get(3, a)</td></tr> + * <tr><td>a[3.0] </td><td>a.get(3, a)</td></tr> + * <tr><td>a["3.0"] </td><td>a.get("3.0", a)</td></tr> + * <tr><td>a[1.1] </td><td>a.get("1.1", a)</td></tr> + * <tr><td>a[-4] </td><td>a.get(-4, a)</td></tr> + * </table> + * <p> + * The values that may be returned are limited to the following: + * <UL> + * <LI>java.lang.Boolean objects</LI> + * <LI>java.lang.String objects</LI> + * <LI>java.lang.Number objects</LI> + * <LI>org.mozilla.javascript.Scriptable objects</LI> + * <LI>null</LI> + * <LI>The value returned by Context.getUndefinedValue()</LI> + * <LI>NOT_FOUND</LI> + * </UL> + * @param name the name of the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Context#getUndefinedValue + */ + public Object get(String name, Scriptable start); + + /** + * Get a property from the object selected by an integral index. + * + * Identical to <code>get(String, Scriptable)</code> except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Scriptable#get(String,Scriptable) + */ + public Object get(int index, Scriptable start); + + /** + * Indicates whether or not a named property is defined in an object. + * + * Does not traverse the prototype chain.<p> + * + * The property is specified by a String name + * as defined for the <code>get</code> method.<p> + * + * @param name the name of the property + * @param start the object in which the lookup began + * @return true if and only if the named property is found in the object + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, String) + */ + public boolean has(String name, Scriptable start); + + /** + * Indicates whether or not an indexed property is defined in an object. + * + * Does not traverse the prototype chain.<p> + * + * The property is specified by an integral index + * as defined for the <code>get</code> method.<p> + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return true if and only if the indexed property is found in the object + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, int) + */ + public boolean has(int index, Scriptable start); + + /** + * Sets a named property in this object. + * <p> + * The property is specified by a string name + * as defined for <code>get</code>. + * <p> + * The possible values that may be passed in are as defined for + * <code>get</code>. A class that implements this method may choose + * to ignore calls to set certain properties, in which case those + * properties are effectively read-only.<p> + * For properties defined in a prototype chain, + * use <code>putProperty</code> in ScriptableObject. <p> + * Note that if a property <i>a</i> is defined in the prototype <i>p</i> + * of an object <i>o</i>, then evaluating <code>o.a = 23</code> will cause + * <code>set</code> to be called on the prototype <i>p</i> with + * <i>o</i> as the <i>start</i> parameter. + * To preserve JavaScript semantics, it is the Scriptable + * object's responsibility to modify <i>o</i>. <p> + * This design allows properties to be defined in prototypes and implemented + * in terms of getters and setters of Java values without consuming slots + * in each instance.<p> + * <p> + * The values that may be set are limited to the following: + * <UL> + * <LI>java.lang.Boolean objects</LI> + * <LI>java.lang.String objects</LI> + * <LI>java.lang.Number objects</LI> + * <LI>org.mozilla.javascript.Scriptable objects</LI> + * <LI>null</LI> + * <LI>The value returned by Context.getUndefinedValue()</LI> + * </UL><p> + * Arbitrary Java objects may be wrapped in a Scriptable by first calling + * <code>Context.toObject</code>. This allows the property of a JavaScript + * object to contain an arbitrary Java object as a value.<p> + * Note that <code>has</code> will be called by the runtime first before + * <code>set</code> is called to determine in which object the + * property is defined. + * Note that this method is not expected to traverse the prototype chain, + * which is different from the ECMA [[Put]] operation. + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(String name, Scriptable start, Object value); + + /** + * Sets an indexed property in this object. + * <p> + * The property is specified by an integral index + * as defined for <code>get</code>.<p> + * + * Identical to <code>put(String, Scriptable, Object)</code> except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(int, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, int, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(int index, Scriptable start, Object value); + + /** + * Removes a property from this object. + * This operation corresponds to the ECMA [[Delete]] except that + * the no result is returned. The runtime will guarantee that this + * method is called only if the property exists. After this method + * is called, the runtime will call Scriptable.has to see if the + * property has been removed in order to determine the boolean + * result of the delete operator as defined by ECMA 11.4.1. + * <p> + * A property can be made permanent by ignoring calls to remove + * it.<p> + * The property is specified by a String name + * as defined for <code>get</code>. + * <p> + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * @param name the identifier for the property + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, String) + */ + public void delete(String name); + + /** + * Removes a property from this object. + * + * The property is specified by an integral index + * as defined for <code>get</code>. + * <p> + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * + * Identical to <code>delete(String)</code> except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, int) + */ + public void delete(int index); + + /** + * Get the prototype of the object. + * @return the prototype + */ + public Scriptable getPrototype(); + + /** + * Set the prototype of the object. + * @param prototype the prototype to set + */ + public void setPrototype(Scriptable prototype); + + /** + * Get the parent scope of the object. + * @return the parent scope + */ + public Scriptable getParentScope(); + + /** + * Set the parent scope of the object. + * @param parent the parent scope to set + */ + public void setParentScope(Scriptable parent); + + /** + * Get an array of property ids. + * + * Not all property ids need be returned. Those properties + * whose ids are not returned are considered non-enumerable. + * + * @return an array of Objects. Each entry in the array is either + * a java.lang.String or a java.lang.Number + */ + public Object[] getIds(); + + /** + * Get the default value of the object with a given hint. + * The hints are String.class for type String, Number.class for type + * Number, Scriptable.class for type Object, and Boolean.class for + * type Boolean. <p> + * + * A <code>hint</code> of null means "no hint". + * + * See ECMA 8.6.2.6. + * + * @param hint the type hint + * @return the default value + */ + public Object getDefaultValue(Class hint); + + /** + * The instanceof operator. + * + * <p> + * The JavaScript code "lhs instanceof rhs" causes rhs.hasInstance(lhs) to + * be called. + * + * <p> + * The return value is implementation dependent so that embedded host objects can + * return an appropriate value. See the JS 1.3 language documentation for more + * detail. + * + * <p>This operator corresponds to the proposed EMCA [[HasInstance]] operator. + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * + * @return an implementation dependent value + */ + public boolean hasInstance(Scriptable instance); +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java new file mode 100644 index 0000000..53de1fc --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/ScriptableObject.java @@ -0,0 +1,2428 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Steve Weiss + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +import java.lang.reflect.*; +import java.util.Hashtable; +import java.io.*; +import org.mozilla.javascript.debug.DebuggableObject; + +/** + * This is the default implementation of the Scriptable interface. This + * class provides convenient default behavior that makes it easier to + * define host objects. + * <p> + * Various properties and methods of JavaScript objects can be conveniently + * defined using methods of ScriptableObject. + * <p> + * Classes extending ScriptableObject must define the getClassName method. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public abstract class ScriptableObject implements Scriptable, Serializable, + DebuggableObject, + ConstProperties +{ + + /** + * The empty property attribute. + * + * Used by getAttributes() and setAttributes(). + * + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int EMPTY = 0x00; + + /** + * Property attribute indicating assignment to this property is ignored. + * + * @see org.mozilla.javascript.ScriptableObject + * #put(String, Scriptable, Object) + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int READONLY = 0x01; + + /** + * Property attribute indicating property is not enumerated. + * + * Only enumerated properties will be returned by getIds(). + * + * @see org.mozilla.javascript.ScriptableObject#getIds() + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int DONTENUM = 0x02; + + /** + * Property attribute indicating property cannot be deleted. + * + * @see org.mozilla.javascript.ScriptableObject#delete(String) + * @see org.mozilla.javascript.ScriptableObject#getAttributes(String) + * @see org.mozilla.javascript.ScriptableObject#setAttributes(String, int) + */ + public static final int PERMANENT = 0x04; + + /** + * Property attribute indicating that this is a const property that has not + * been assigned yet. The first 'const' assignment to the property will + * clear this bit. + */ + public static final int UNINITIALIZED_CONST = 0x08; + + public static final int CONST = PERMANENT|READONLY|UNINITIALIZED_CONST; + /** + * The prototype of this object. + */ + private Scriptable prototypeObject; + + /** + * The parent scope of this object. + */ + private Scriptable parentScopeObject; + + private static final Slot REMOVED = new Slot(null, 0, READONLY); + + static { + REMOVED.wasDeleted = 1; + } + + private transient Slot[] slots; + // If count >= 0, it gives number of keys or if count < 0, + // it indicates sealed object where ~count gives number of keys + private int count; + + // cache; may be removed for smaller memory footprint + private transient Slot lastAccess = REMOVED; + + // associated values are not serialized + private transient volatile Hashtable associatedValues; + + private static final int SLOT_QUERY = 1; + private static final int SLOT_MODIFY = 2; + private static final int SLOT_REMOVE = 3; + private static final int SLOT_MODIFY_GETTER_SETTER = 4; + private static final int SLOT_MODIFY_CONST = 5; + + private static class Slot implements Serializable + { + static final long serialVersionUID = -3539051633409902634L; + + String name; // This can change due to caching + int indexOrHash; + private volatile short attributes; + transient volatile byte wasDeleted; + volatile Object value; + transient volatile Slot next; + + Slot(String name, int indexOrHash, int attributes) + { + this.name = name; + this.indexOrHash = indexOrHash; + this.attributes = (short)attributes; + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + if (name != null) { + indexOrHash = name.hashCode(); + } + } + + final int getAttributes() + { + return attributes; + } + + final synchronized void setAttributes(int value) + { + checkValidAttributes(value); + attributes = (short)value; + } + + final void checkNotReadonly() + { + if ((attributes & READONLY) != 0) { + String str = (name != null ? name + : Integer.toString(indexOrHash)); + throw Context.reportRuntimeError1("msg.modify.readonly", str); + } + } + + } + + private static final class GetterSlot extends Slot + { + static final long serialVersionUID = -4900574849788797588L; + + Object getter; + Object setter; + + GetterSlot(String name, int indexOrHash, int attributes) + { + super(name, indexOrHash, attributes); + } + } + + static void checkValidAttributes(int attributes) + { + final int mask = READONLY | DONTENUM | PERMANENT | UNINITIALIZED_CONST; + if ((attributes & ~mask) != 0) { + throw new IllegalArgumentException(String.valueOf(attributes)); + } + } + + public ScriptableObject() + { + } + + public ScriptableObject(Scriptable scope, Scriptable prototype) + { + if (scope == null) + throw new IllegalArgumentException(); + + parentScopeObject = scope; + prototypeObject = prototype; + } + + /** + * Return the name of the class. + * + * This is typically the same name as the constructor. + * Classes extending ScriptableObject must implement this abstract + * method. + */ + public abstract String getClassName(); + + /** + * Returns true if the named property is defined. + * + * @param name the name of the property + * @param start the object in which the lookup began + * @return true if and only if the property was found in the object + */ + public boolean has(String name, Scriptable start) + { + return null != getSlot(name, 0, SLOT_QUERY); + } + + /** + * Returns true if the property index is defined. + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return true if and only if the property was found in the object + */ + public boolean has(int index, Scriptable start) + { + return null != getSlot(null, index, SLOT_QUERY); + } + + /** + * Returns the value of the named property or NOT_FOUND. + * + * If the property was created using defineProperty, the + * appropriate getter method is called. + * + * @param name the name of the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + */ + public Object get(String name, Scriptable start) + { + return getImpl(name, 0, start); + } + + /** + * Returns the value of the indexed property or NOT_FOUND. + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + */ + public Object get(int index, Scriptable start) + { + return getImpl(null, index, start); + } + + /** + * Sets the value of the named property, creating it if need be. + * + * If the property was created using defineProperty, the + * appropriate setter method is called. <p> + * + * If the property's attributes include READONLY, no action is + * taken. + * This method will actually set the property in the start + * object. + * + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + */ + public void put(String name, Scriptable start, Object value) + { + if (putImpl(name, 0, start, value, EMPTY)) + return; + + if (start == this) throw Kit.codeBug(); + start.put(name, start, value); + } + + /** + * Sets the value of the indexed property, creating it if need be. + * + * @param index the numeric index for the property + * @param start the object whose property is being set + * @param value value to set the property to + */ + public void put(int index, Scriptable start, Object value) + { + if (putImpl(null, index, start, value, EMPTY)) + return; + + if (start == this) throw Kit.codeBug(); + start.put(index, start, value); + } + + /** + * Removes a named property from the object. + * + * If the property is not found, or it has the PERMANENT attribute, + * no action is taken. + * + * @param name the name of the property + */ + public void delete(String name) + { + checkNotSealed(name, 0); + accessSlot(name, 0, SLOT_REMOVE); + } + + /** + * Removes the indexed property from the object. + * + * If the property is not found, or it has the PERMANENT attribute, + * no action is taken. + * + * @param index the numeric index for the property + */ + public void delete(int index) + { + checkNotSealed(null, index); + accessSlot(null, index, SLOT_REMOVE); + } + + /** + * Sets the value of the named const property, creating it if need be. + * + * If the property was created using defineProperty, the + * appropriate setter method is called. <p> + * + * If the property's attributes include READONLY, no action is + * taken. + * This method will actually set the property in the start + * object. + * + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + */ + public void putConst(String name, Scriptable start, Object value) + { + if (putImpl(name, 0, start, value, READONLY)) + return; + + if (start == this) throw Kit.codeBug(); + if (start instanceof ConstProperties) + ((ConstProperties)start).putConst(name, start, value); + else + start.put(name, start, value); + } + + public void defineConst(String name, Scriptable start) + { + if (putImpl(name, 0, start, Undefined.instance, UNINITIALIZED_CONST)) + return; + + if (start == this) throw Kit.codeBug(); + if (start instanceof ConstProperties) + ((ConstProperties)start).defineConst(name, start); + } + /** + * Returns true if the named property is defined as a const on this object. + * @param name + * @return true if the named property is defined as a const, false + * otherwise. + */ + public boolean isConst(String name) + { + Slot slot = getSlot(name, 0, SLOT_QUERY); + if (slot == null) { + return false; + } + return (slot.getAttributes() & (PERMANENT|READONLY)) == + (PERMANENT|READONLY); + + } + /** + * @deprecated Use {@link #getAttributes(String name)}. The engine always + * ignored the start argument. + */ + public final int getAttributes(String name, Scriptable start) + { + return getAttributes(name); + } + + /** + * @deprecated Use {@link #getAttributes(int index)}. The engine always + * ignored the start argument. + */ + public final int getAttributes(int index, Scriptable start) + { + return getAttributes(index); + } + + /** + * @deprecated Use {@link #setAttributes(String name, int attributes)}. + * The engine always ignored the start argument. + */ + public final void setAttributes(String name, Scriptable start, + int attributes) + { + setAttributes(name, attributes); + } + + /** + * @deprecated Use {@link #setAttributes(int index, int attributes)}. + * The engine always ignored the start argument. + */ + public void setAttributes(int index, Scriptable start, + int attributes) + { + setAttributes(index, attributes); + } + + /** + * Get the attributes of a named property. + * + * The property is specified by <code>name</code> + * as defined for <code>has</code>.<p> + * + * @param name the identifier for the property + * @return the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public int getAttributes(String name) + { + return findAttributeSlot(name, 0, SLOT_QUERY).getAttributes(); + } + + /** + * Get the attributes of an indexed property. + * + * @param index the numeric index for the property + * @exception EvaluatorException if the named property is not found + * is not found + * @return the bitset of attributes + * @see org.mozilla.javascript.ScriptableObject#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public int getAttributes(int index) + { + return findAttributeSlot(null, index, SLOT_QUERY).getAttributes(); + } + + /** + * Set the attributes of a named property. + * + * The property is specified by <code>name</code> + * as defined for <code>has</code>.<p> + * + * The possible attributes are READONLY, DONTENUM, + * and PERMANENT. Combinations of attributes + * are expressed by the bitwise OR of attributes. + * EMPTY is the state of no attributes set. Any unused + * bits are reserved for future use. + * + * @param name the name of the property + * @param attributes the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public void setAttributes(String name, int attributes) + { + checkNotSealed(name, 0); + findAttributeSlot(name, 0, SLOT_MODIFY).setAttributes(attributes); + } + + /** + * Set the attributes of an indexed property. + * + * @param index the numeric index for the property + * @param attributes the bitset of attributes + * @exception EvaluatorException if the named property is not found + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject#DONTENUM + * @see org.mozilla.javascript.ScriptableObject#PERMANENT + * @see org.mozilla.javascript.ScriptableObject#EMPTY + */ + public void setAttributes(int index, int attributes) + { + checkNotSealed(null, index); + findAttributeSlot(null, index, SLOT_MODIFY).setAttributes(attributes); + } + + /** + * XXX: write docs. + */ + public void setGetterOrSetter(String name, int index, + Callable getterOrSeter, boolean isSetter) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + + checkNotSealed(name, index); + GetterSlot gslot = (GetterSlot)getSlot(name, index, + SLOT_MODIFY_GETTER_SETTER); + gslot.checkNotReadonly(); + if (isSetter) { + gslot.setter = getterOrSeter; + } else { + gslot.getter = getterOrSeter; + } + gslot.value = Undefined.instance; + } + + /** + * Get the getter or setter for a given property. Used by __lookupGetter__ + * and __lookupSetter__. + * + * @param name Name of the object. If nonnull, index must be 0. + * @param index Index of the object. If nonzero, name must be null. + * @param isSetter If true, return the setter, otherwise return the getter. + * @exception IllegalArgumentException if both name and index are nonnull + * and nonzero respectively. + * @return Null if the property does not exist. Otherwise returns either + * the getter or the setter for the property, depending on + * the value of isSetter (may be undefined if unset). + */ + public Object getGetterOrSetter(String name, int index, boolean isSetter) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) + return null; + if (slot instanceof GetterSlot) { + GetterSlot gslot = (GetterSlot)slot; + Object result = isSetter ? gslot.setter : gslot.getter; + return result != null ? result : Undefined.instance; + } else + return Undefined.instance; + } + + /** + * Returns whether a property is a getter or a setter + * @param name property name + * @param index property index + * @param setter true to check for a setter, false for a getter + * @return whether the property is a getter or a setter + */ + protected boolean isGetterOrSetter(String name, int index, boolean setter) { + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot instanceof GetterSlot) { + if (setter && ((GetterSlot)slot).setter != null) return true; + if (!setter && ((GetterSlot)slot).getter != null) return true; + } + return false; + } + + void addLazilyInitializedValue(String name, int index, + LazilyLoadedCtor init, int attributes) + { + if (name != null && index != 0) + throw new IllegalArgumentException(name); + checkNotSealed(name, index); + GetterSlot gslot = (GetterSlot)getSlot(name, index, + SLOT_MODIFY_GETTER_SETTER); + gslot.setAttributes(attributes); + gslot.getter = null; + gslot.setter = null; + gslot.value = init; + } + + /** + * Returns the prototype of the object. + */ + public Scriptable getPrototype() + { + return prototypeObject; + } + + /** + * Sets the prototype of the object. + */ + public void setPrototype(Scriptable m) + { + prototypeObject = m; + } + + /** + * Returns the parent (enclosing) scope of the object. + */ + public Scriptable getParentScope() + { + return parentScopeObject; + } + + /** + * Sets the parent (enclosing) scope of the object. + */ + public void setParentScope(Scriptable m) + { + parentScopeObject = m; + } + + /** + * Returns an array of ids for the properties of the object. + * + * <p>Any properties with the attribute DONTENUM are not listed. <p> + * + * @return an array of java.lang.Objects with an entry for every + * listed property. Properties accessed via an integer index will + * have a corresponding + * Integer entry in the returned array. Properties accessed by + * a String will have a String entry in the returned array. + */ + public Object[] getIds() { + return getIds(false); + } + + /** + * Returns an array of ids for the properties of the object. + * + * <p>All properties, even those with attribute DONTENUM, are listed. <p> + * + * @return an array of java.lang.Objects with an entry for every + * listed property. Properties accessed via an integer index will + * have a corresponding + * Integer entry in the returned array. Properties accessed by + * a String will have a String entry in the returned array. + */ + public Object[] getAllIds() { + return getIds(true); + } + + /** + * Implements the [[DefaultValue]] internal method. + * + * <p>Note that the toPrimitive conversion is a no-op for + * every type other than Object, for which [[DefaultValue]] + * is called. See ECMA 9.1.<p> + * + * A <code>hint</code> of null means "no hint". + * + * @param typeHint the type hint + * @return the default value for the object + * + * See ECMA 8.6.2.6. + */ + public Object getDefaultValue(Class typeHint) + { + return getDefaultValue(this, typeHint); + } + + public static Object getDefaultValue(Scriptable object, Class typeHint) + { + Context cx = null; + for (int i=0; i < 2; i++) { + boolean tryToString; + if (typeHint == ScriptRuntime.StringClass) { + tryToString = (i == 0); + } else { + tryToString = (i == 1); + } + + String methodName; + Object[] args; + if (tryToString) { + methodName = "toString"; + args = ScriptRuntime.emptyArgs; + } else { + methodName = "valueOf"; + args = new Object[1]; + String hint; + if (typeHint == null) { + hint = "undefined"; + } else if (typeHint == ScriptRuntime.StringClass) { + hint = "string"; + } else if (typeHint == ScriptRuntime.ScriptableClass) { + hint = "object"; + } else if (typeHint == ScriptRuntime.FunctionClass) { + hint = "function"; + } else if (typeHint == ScriptRuntime.BooleanClass + || typeHint == Boolean.TYPE) + { + hint = "boolean"; + } else if (typeHint == ScriptRuntime.NumberClass || + typeHint == ScriptRuntime.ByteClass || + typeHint == Byte.TYPE || + typeHint == ScriptRuntime.ShortClass || + typeHint == Short.TYPE || + typeHint == ScriptRuntime.IntegerClass || + typeHint == Integer.TYPE || + typeHint == ScriptRuntime.FloatClass || + typeHint == Float.TYPE || + typeHint == ScriptRuntime.DoubleClass || + typeHint == Double.TYPE) + { + hint = "number"; + } else { + throw Context.reportRuntimeError1( + "msg.invalid.type", typeHint.toString()); + } + args[0] = hint; + } + Object v = getProperty(object, methodName); + if (!(v instanceof Function)) + continue; + Function fun = (Function) v; + if (cx == null) + cx = Context.getContext(); + v = fun.call(cx, fun.getParentScope(), object, args); + if (v != null) { + if (!(v instanceof Scriptable)) { + return v; + } + if (typeHint == ScriptRuntime.ScriptableClass + || typeHint == ScriptRuntime.FunctionClass) + { + return v; + } + if (tryToString && v instanceof Wrapper) { + // Let a wrapped java.lang.String pass for a primitive + // string. + Object u = ((Wrapper)v).unwrap(); + if (u instanceof String) + return u; + } + } + } + // fall through to error + String arg = (typeHint == null) ? "undefined" : typeHint.getName(); + throw ScriptRuntime.typeError1("msg.default.value", arg); + } + + /** + * Implements the instanceof operator. + * + * <p>This operator has been proposed to ECMA. + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * @return true if "this" appears in value's prototype chain + * + */ + public boolean hasInstance(Scriptable instance) { + // Default for JS objects (other than Function) is to do prototype + // chasing. This will be overridden in NativeFunction and non-JS + // objects. + + return ScriptRuntime.jsDelegatesTo(instance, this); + } + + /** + * Emulate the SpiderMonkey (and Firefox) feature of allowing + * custom objects to avoid detection by normal "object detection" + * code patterns. This is used to implement document.all. + * See https://bugzilla.mozilla.org/show_bug.cgi?id=412247. + * This is an analog to JOF_DETECTING from SpiderMonkey; see + * https://bugzilla.mozilla.org/show_bug.cgi?id=248549. + * Other than this special case, embeddings should return false. + * @return true if this object should avoid object detection + * @since 1.7R1 + */ + public boolean avoidObjectDetection() { + return false; + } + + /** + * Custom <tt>==</tt> operator. + * Must return {@link Scriptable#NOT_FOUND} if this object does not + * have custom equality operator for the given value, + * <tt>Boolean.TRUE</tt> if this object is equivalent to <tt>value</tt>, + * <tt>Boolean.FALSE</tt> if this object is not equivalent to + * <tt>value</tt>. + * <p> + * The default implementation returns Boolean.TRUE + * if <tt>this == value</tt> or {@link Scriptable#NOT_FOUND} otherwise. + * It indicates that by default custom equality is available only if + * <tt>value</tt> is <tt>this</tt> in which case true is returned. + */ + protected Object equivalentValues(Object value) + { + return (this == value) ? Boolean.TRUE : Scriptable.NOT_FOUND; + } + + /** + * Defines JavaScript objects from a Java class that implements Scriptable. + * + * If the given class has a method + * <pre> + * static void init(Context cx, Scriptable scope, boolean sealed);</pre> + * + * or its compatibility form + * <pre> + * static void init(Scriptable scope);</pre> + * + * then it is invoked and no further initialization is done.<p> + * + * However, if no such a method is found, then the class's constructors and + * methods are used to initialize a class in the following manner.<p> + * + * First, the zero-parameter constructor of the class is called to + * create the prototype. If no such constructor exists, + * a {@link EvaluatorException} is thrown. <p> + * + * Next, all methods are scanned for special prefixes that indicate that they + * have special meaning for defining JavaScript objects. + * These special prefixes are + * <ul> + * <li><code>jsFunction_</code> for a JavaScript function + * <li><code>jsStaticFunction_</code> for a JavaScript function that + * is a property of the constructor + * <li><code>jsGet_</code> for a getter of a JavaScript property + * <li><code>jsSet_</code> for a setter of a JavaScript property + * <li><code>jsConstructor</code> for a JavaScript function that + * is the constructor + * </ul><p> + * + * If the method's name begins with "jsFunction_", a JavaScript function + * is created with a name formed from the rest of the Java method name + * following "jsFunction_". So a Java method named "jsFunction_foo" will + * define a JavaScript method "foo". Calling this JavaScript function + * will cause the Java method to be called. The parameters of the method + * must be of number and types as defined by the FunctionObject class. + * The JavaScript function is then added as a property + * of the prototype. <p> + * + * If the method's name begins with "jsStaticFunction_", it is handled + * similarly except that the resulting JavaScript function is added as a + * property of the constructor object. The Java method must be static. + * + * If the method's name begins with "jsGet_" or "jsSet_", the method is + * considered to define a property. Accesses to the defined property + * will result in calls to these getter and setter methods. If no + * setter is defined, the property is defined as READONLY.<p> + * + * If the method's name is "jsConstructor", the method is + * considered to define the body of the constructor. Only one + * method of this name may be defined. + * If no method is found that can serve as constructor, a Java + * constructor will be selected to serve as the JavaScript + * constructor in the following manner. If the class has only one + * Java constructor, that constructor is used to define + * the JavaScript constructor. If the the class has two constructors, + * one must be the zero-argument constructor (otherwise an + * {@link EvaluatorException} would have already been thrown + * when the prototype was to be created). In this case + * the Java constructor with one or more parameters will be used + * to define the JavaScript constructor. If the class has three + * or more constructors, an {@link EvaluatorException} + * will be thrown.<p> + * + * Finally, if there is a method + * <pre> + * static void finishInit(Scriptable scope, FunctionObject constructor, + * Scriptable prototype)</pre> + * + * it will be called to finish any initialization. The <code>scope</code> + * argument will be passed, along with the newly created constructor and + * the newly created prototype.<p> + * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @see org.mozilla.javascript.Function + * @see org.mozilla.javascript.FunctionObject + * @see org.mozilla.javascript.ScriptableObject#READONLY + * @see org.mozilla.javascript.ScriptableObject + * #defineProperty(String, Class, int) + */ + public static void defineClass(Scriptable scope, Class clazz) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + defineClass(scope, clazz, false, false); + } + + /** + * Defines JavaScript objects from a Java class, optionally + * allowing sealing. + * + * Similar to <code>defineClass(Scriptable scope, Class clazz)</code> + * except that sealing is allowed. An object that is sealed cannot have + * properties added or removed. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. The class must implement Scriptable. + * @param sealed Whether or not to create sealed standard objects that + * cannot be modified. + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @since 1.4R3 + */ + public static void defineClass(Scriptable scope, Class clazz, + boolean sealed) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + defineClass(scope, clazz, sealed, false); + } + + /** + * Defines JavaScript objects from a Java class, optionally + * allowing sealing and mapping of Java inheritance to JavaScript + * prototype-based inheritance. + * + * Similar to <code>defineClass(Scriptable scope, Class clazz)</code> + * except that sealing and inheritance mapping are allowed. An object + * that is sealed cannot have properties added or removed. Note that + * sealing is not allowed in the current ECMA/ISO language specification, + * but is likely for the next version. + * + * @param scope The scope in which to define the constructor. + * @param clazz The Java class to use to define the JavaScript objects + * and properties. The class must implement Scriptable. + * @param sealed Whether or not to create sealed standard objects that + * cannot be modified. + * @param mapInheritance Whether or not to map Java inheritance to + * JavaScript prototype-based inheritance. + * @return the class name for the prototype of the specified class + * @exception IllegalAccessException if access is not available + * to a reflected class member + * @exception InstantiationException if unable to instantiate + * the named class + * @exception InvocationTargetException if an exception is thrown + * during execution of methods of the named class + * @since 1.6R2 + */ + public static String defineClass(Scriptable scope, Class clazz, + boolean sealed, boolean mapInheritance) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + BaseFunction ctor = buildClassCtor(scope, clazz, sealed, + mapInheritance); + if (ctor == null) + return null; + String name = ctor.getClassPrototype().getClassName(); + defineProperty(scope, name, ctor, ScriptableObject.DONTENUM); + return name; + } + + static BaseFunction buildClassCtor(Scriptable scope, Class clazz, + boolean sealed, + boolean mapInheritance) + throws IllegalAccessException, InstantiationException, + InvocationTargetException + { + Method[] methods = FunctionObject.getMethodList(clazz); + for (int i=0; i < methods.length; i++) { + Method method = methods[i]; + if (!method.getName().equals("init")) + continue; + Class[] parmTypes = method.getParameterTypes(); + if (parmTypes.length == 3 && + parmTypes[0] == ScriptRuntime.ContextClass && + parmTypes[1] == ScriptRuntime.ScriptableClass && + parmTypes[2] == Boolean.TYPE && + Modifier.isStatic(method.getModifiers())) + { + Object args[] = { Context.getContext(), scope, + sealed ? Boolean.TRUE : Boolean.FALSE }; + method.invoke(null, args); + return null; + } + if (parmTypes.length == 1 && + parmTypes[0] == ScriptRuntime.ScriptableClass && + Modifier.isStatic(method.getModifiers())) + { + Object args[] = { scope }; + method.invoke(null, args); + return null; + } + + } + + // If we got here, there isn't an "init" method with the right + // parameter types. + + Constructor[] ctors = clazz.getConstructors(); + Constructor protoCtor = null; + for (int i=0; i < ctors.length; i++) { + if (ctors[i].getParameterTypes().length == 0) { + protoCtor = ctors[i]; + break; + } + } + if (protoCtor == null) { + throw Context.reportRuntimeError1( + "msg.zero.arg.ctor", clazz.getName()); + } + + Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs); + String className = proto.getClassName(); + + // Set the prototype's prototype, trying to map Java inheritance to JS + // prototype-based inheritance if requested to do so. + Scriptable superProto = null; + if (mapInheritance) { + Class superClass = clazz.getSuperclass(); + if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass) + && !Modifier.isAbstract(superClass.getModifiers())) { + String name = ScriptableObject.defineClass(scope, superClass, sealed, mapInheritance); + if (name != null) { + superProto = ScriptableObject.getClassPrototype(scope, name); + } + } + } + if (superProto == null) { + superProto = ScriptableObject.getObjectPrototype(scope); + } + proto.setPrototype(superProto); + + // Find out whether there are any methods that begin with + // "js". If so, then only methods that begin with special + // prefixes will be defined as JavaScript entities. + final String functionPrefix = "jsFunction_"; + final String staticFunctionPrefix = "jsStaticFunction_"; + final String getterPrefix = "jsGet_"; + final String setterPrefix = "jsSet_"; + final String ctorName = "jsConstructor"; + + Member ctorMember = FunctionObject.findSingleMethod(methods, ctorName); + + if (ctorMember == null) { + if (ctors.length == 1) { + ctorMember = ctors[0]; + } else if (ctors.length == 2) { + if (ctors[0].getParameterTypes().length == 0) + ctorMember = ctors[1]; + else if (ctors[1].getParameterTypes().length == 0) + ctorMember = ctors[0]; + } + if (ctorMember == null) { + throw Context.reportRuntimeError1( + "msg.ctor.multiple.parms", clazz.getName()); + } + } + + FunctionObject ctor = new FunctionObject(className, ctorMember, scope); + if (ctor.isVarArgsMethod()) { + throw Context.reportRuntimeError1 + ("msg.varargs.ctor", ctorMember.getName()); + } + ctor.initAsConstructor(scope, proto); + + Method finishInit = null; + for (int i=0; i < methods.length; i++) { + if (methods[i] == ctorMember) { + continue; + } + String name = methods[i].getName(); + if (name.equals("finishInit")) { + Class[] parmTypes = methods[i].getParameterTypes(); + if (parmTypes.length == 3 && + parmTypes[0] == ScriptRuntime.ScriptableClass && + parmTypes[1] == FunctionObject.class && + parmTypes[2] == ScriptRuntime.ScriptableClass && + Modifier.isStatic(methods[i].getModifiers())) + { + finishInit = methods[i]; + continue; + } + } + // ignore any compiler generated methods. + if (name.indexOf('$') != -1) + continue; + if (name.equals(ctorName)) + continue; + + String prefix = null; + if (name.startsWith(functionPrefix)) { + prefix = functionPrefix; + } else if (name.startsWith(staticFunctionPrefix)) { + prefix = staticFunctionPrefix; + if (!Modifier.isStatic(methods[i].getModifiers())) { + throw Context.reportRuntimeError( + "jsStaticFunction must be used with static method."); + } + } else if (name.startsWith(getterPrefix)) { + prefix = getterPrefix; + } else if (name.startsWith(setterPrefix)) { + prefix = setterPrefix; + } else { + continue; + } + name = name.substring(prefix.length()); + if (prefix == setterPrefix) + continue; // deal with set when we see get + if (prefix == getterPrefix) { + if (!(proto instanceof ScriptableObject)) { + throw Context.reportRuntimeError2( + "msg.extend.scriptable", + proto.getClass().toString(), name); + } + Method setter = FunctionObject.findSingleMethod( + methods, + setterPrefix + name); + int attr = ScriptableObject.PERMANENT | + ScriptableObject.DONTENUM | + (setter != null ? 0 + : ScriptableObject.READONLY); + ((ScriptableObject) proto).defineProperty(name, null, + methods[i], setter, + attr); + continue; + } + + FunctionObject f = new FunctionObject(name, methods[i], proto); + if (f.isVarArgsConstructor()) { + throw Context.reportRuntimeError1 + ("msg.varargs.fun", ctorMember.getName()); + } + Scriptable dest = prefix == staticFunctionPrefix + ? ctor + : proto; + defineProperty(dest, name, f, DONTENUM); + if (sealed) { + f.sealObject(); + } + } + + // Call user code to complete initialization if necessary. + if (finishInit != null) { + Object[] finishArgs = { scope, ctor, proto }; + finishInit.invoke(null, finishArgs); + } + + // Seal the object if necessary. + if (sealed) { + ctor.sealObject(); + if (proto instanceof ScriptableObject) { + ((ScriptableObject) proto).sealObject(); + } + } + + return ctor; + } + + /** + * Define a JavaScript property. + * + * Creates the property with an initial value and sets its attributes. + * + * @param propertyName the name of the property to define. + * @param value the initial value of the property + * @param attributes the attributes of the JavaScript property + * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) + */ + public void defineProperty(String propertyName, Object value, + int attributes) + { + checkNotSealed(propertyName, 0); + put(propertyName, this, value); + setAttributes(propertyName, attributes); + } + + /** + * Utility method to add properties to arbitrary Scriptable object. + * If destination is instance of ScriptableObject, calls + * defineProperty there, otherwise calls put in destination + * ignoring attributes + */ + public static void defineProperty(Scriptable destination, + String propertyName, Object value, + int attributes) + { + if (!(destination instanceof ScriptableObject)) { + destination.put(propertyName, destination, value); + return; + } + ScriptableObject so = (ScriptableObject)destination; + so.defineProperty(propertyName, value, attributes); + } + + /** + * Utility method to add properties to arbitrary Scriptable object. + * If destination is instance of ScriptableObject, calls + * defineProperty there, otherwise calls put in destination + * ignoring attributes + */ + public static void defineConstProperty(Scriptable destination, + String propertyName) + { + if (destination instanceof ConstProperties) { + ConstProperties cp = (ConstProperties)destination; + cp.defineConst(propertyName, destination); + } else + defineProperty(destination, propertyName, Undefined.instance, CONST); + } + + /** + * Define a JavaScript property with getter and setter side effects. + * + * If the setter is not found, the attribute READONLY is added to + * the given attributes. <p> + * + * The getter must be a method with zero parameters, and the setter, if + * found, must be a method with one parameter.<p> + * + * @param propertyName the name of the property to define. This name + * also affects the name of the setter and getter + * to search for. If the propertyId is "foo", then + * <code>clazz</code> will be searched for "getFoo" + * and "setFoo" methods. + * @param clazz the Java class to search for the getter and setter + * @param attributes the attributes of the JavaScript property + * @see org.mozilla.javascript.Scriptable#put(String, Scriptable, Object) + */ + public void defineProperty(String propertyName, Class clazz, + int attributes) + { + int length = propertyName.length(); + if (length == 0) throw new IllegalArgumentException(); + char[] buf = new char[3 + length]; + propertyName.getChars(0, length, buf, 3); + buf[3] = Character.toUpperCase(buf[3]); + buf[0] = 'g'; + buf[1] = 'e'; + buf[2] = 't'; + String getterName = new String(buf); + buf[0] = 's'; + String setterName = new String(buf); + + Method[] methods = FunctionObject.getMethodList(clazz); + Method getter = FunctionObject.findSingleMethod(methods, getterName); + Method setter = FunctionObject.findSingleMethod(methods, setterName); + if (setter == null) + attributes |= ScriptableObject.READONLY; + defineProperty(propertyName, null, getter, + setter == null ? null : setter, attributes); + } + + /** + * Define a JavaScript property. + * + * Use this method only if you wish to define getters and setters for + * a given property in a ScriptableObject. To create a property without + * special getter or setter side effects, use + * <code>defineProperty(String,int)</code>. + * + * If <code>setter</code> is null, the attribute READONLY is added to + * the given attributes.<p> + * + * Several forms of getters or setters are allowed. In all cases the + * type of the value parameter can be any one of the following types: + * Object, String, boolean, Scriptable, byte, short, int, long, float, + * or double. The runtime will perform appropriate conversions based + * upon the type of the parameter (see description in FunctionObject). + * The first forms are nonstatic methods of the class referred to + * by 'this': + * <pre> + * Object getFoo(); + * void setFoo(SomeType value);</pre> + * Next are static methods that may be of any class; the object whose + * property is being accessed is passed in as an extra argument: + * <pre> + * static Object getFoo(Scriptable obj); + * static void setFoo(Scriptable obj, SomeType value);</pre> + * Finally, it is possible to delegate to another object entirely using + * the <code>delegateTo</code> parameter. In this case the methods are + * nonstatic methods of the class delegated to, and the object whose + * property is being accessed is passed in as an extra argument: + * <pre> + * Object getFoo(Scriptable obj); + * void setFoo(Scriptable obj, SomeType value);</pre> + * + * @param propertyName the name of the property to define. + * @param delegateTo an object to call the getter and setter methods on, + * or null, depending on the form used above. + * @param getter the method to invoke to get the value of the property + * @param setter the method to invoke to set the value of the property + * @param attributes the attributes of the JavaScript property + */ + public void defineProperty(String propertyName, Object delegateTo, + Method getter, Method setter, int attributes) + { + MemberBox getterBox = null; + if (getter != null) { + getterBox = new MemberBox(getter); + + boolean delegatedForm; + if (!Modifier.isStatic(getter.getModifiers())) { + delegatedForm = (delegateTo != null); + getterBox.delegateTo = delegateTo; + } else { + delegatedForm = true; + // Ignore delegateTo for static getter but store + // non-null delegateTo indicator. + getterBox.delegateTo = Void.TYPE; + } + + String errorId = null; + Class[] parmTypes = getter.getParameterTypes(); + if (parmTypes.length == 0) { + if (delegatedForm) { + errorId = "msg.obj.getter.parms"; + } + } else if (parmTypes.length == 1) { + Object argType = parmTypes[0]; + // Allow ScriptableObject for compatibility + if (!(argType == ScriptRuntime.ScriptableClass || + argType == ScriptRuntime.ScriptableObjectClass)) + { + errorId = "msg.bad.getter.parms"; + } else if (!delegatedForm) { + errorId = "msg.bad.getter.parms"; + } + } else { + errorId = "msg.bad.getter.parms"; + } + if (errorId != null) { + throw Context.reportRuntimeError1(errorId, getter.toString()); + } + } + + MemberBox setterBox = null; + if (setter != null) { + if (setter.getReturnType() != Void.TYPE) + throw Context.reportRuntimeError1("msg.setter.return", + setter.toString()); + + setterBox = new MemberBox(setter); + + boolean delegatedForm; + if (!Modifier.isStatic(setter.getModifiers())) { + delegatedForm = (delegateTo != null); + setterBox.delegateTo = delegateTo; + } else { + delegatedForm = true; + // Ignore delegateTo for static setter but store + // non-null delegateTo indicator. + setterBox.delegateTo = Void.TYPE; + } + + String errorId = null; + Class[] parmTypes = setter.getParameterTypes(); + if (parmTypes.length == 1) { + if (delegatedForm) { + errorId = "msg.setter2.expected"; + } + } else if (parmTypes.length == 2) { + Object argType = parmTypes[0]; + // Allow ScriptableObject for compatibility + if (!(argType == ScriptRuntime.ScriptableClass || + argType == ScriptRuntime.ScriptableObjectClass)) + { + errorId = "msg.setter2.parms"; + } else if (!delegatedForm) { + errorId = "msg.setter1.parms"; + } + } else { + errorId = "msg.setter.parms"; + } + if (errorId != null) { + throw Context.reportRuntimeError1(errorId, setter.toString()); + } + } + + GetterSlot gslot = (GetterSlot)getSlot(propertyName, 0, + SLOT_MODIFY_GETTER_SETTER); + gslot.setAttributes(attributes); + gslot.getter = getterBox; + gslot.setter = setterBox; + } + + /** + * Search for names in a class, adding the resulting methods + * as properties. + * + * <p> Uses reflection to find the methods of the given names. Then + * FunctionObjects are constructed from the methods found, and + * are added to this object as properties with the given names. + * + * @param names the names of the Methods to add as function properties + * @param clazz the class to search for the Methods + * @param attributes the attributes of the new properties + * @see org.mozilla.javascript.FunctionObject + */ + public void defineFunctionProperties(String[] names, Class clazz, + int attributes) + { + Method[] methods = FunctionObject.getMethodList(clazz); + for (int i=0; i < names.length; i++) { + String name = names[i]; + Method m = FunctionObject.findSingleMethod(methods, name); + if (m == null) { + throw Context.reportRuntimeError2( + "msg.method.not.found", name, clazz.getName()); + } + FunctionObject f = new FunctionObject(name, m, this); + defineProperty(name, f, attributes); + } + } + + /** + * Get the Object.prototype property. + * See ECMA 15.2.4. + */ + public static Scriptable getObjectPrototype(Scriptable scope) { + return getClassPrototype(scope, "Object"); + } + + /** + * Get the Function.prototype property. + * See ECMA 15.3.4. + */ + public static Scriptable getFunctionPrototype(Scriptable scope) { + return getClassPrototype(scope, "Function"); + } + + /** + * Get the prototype for the named class. + * + * For example, <code>getClassPrototype(s, "Date")</code> will first + * walk up the parent chain to find the outermost scope, then will + * search that scope for the Date constructor, and then will + * return Date.prototype. If any of the lookups fail, or + * the prototype is not a JavaScript object, then null will + * be returned. + * + * @param scope an object in the scope chain + * @param className the name of the constructor + * @return the prototype for the named class, or null if it + * cannot be found. + */ + public static Scriptable getClassPrototype(Scriptable scope, + String className) + { + scope = getTopLevelScope(scope); + Object ctor = getProperty(scope, className); + Object proto; + if (ctor instanceof BaseFunction) { + proto = ((BaseFunction)ctor).getPrototypeProperty(); + } else if (ctor instanceof Scriptable) { + Scriptable ctorObj = (Scriptable)ctor; + proto = ctorObj.get("prototype", ctorObj); + } else { + return null; + } + if (proto instanceof Scriptable) { + return (Scriptable)proto; + } + return null; + } + + /** + * Get the global scope. + * + * <p>Walks the parent scope chain to find an object with a null + * parent scope (the global object). + * + * @param obj a JavaScript object + * @return the corresponding global scope + */ + public static Scriptable getTopLevelScope(Scriptable obj) + { + for (;;) { + Scriptable parent = obj.getParentScope(); + if (parent == null) { + return obj; + } + obj = parent; + } + } + + // APPJET + public static Scriptable getVeryTopLevelScope(Scriptable obj) { + return ScriptRuntime.getLibraryScopeOrNull(obj); + } + + /** + * Seal this object. + * + * A sealed object may not have properties added or removed. Once + * an object is sealed it may not be unsealed. + * + * @since 1.4R3 + */ + public synchronized void sealObject() { + if (count >= 0) { + count = ~count; + } + } + + /** + * Return true if this object is sealed. + * + * It is an error to attempt to add or remove properties to + * a sealed object. + * + * @return true if sealed, false otherwise. + * @since 1.4R3 + */ + public final boolean isSealed() { + return count < 0; + } + + private void checkNotSealed(String name, int index) + { + if (!isSealed()) + return; + + String str = (name != null) ? name : Integer.toString(index); + throw Context.reportRuntimeError1("msg.modify.sealed", str); + } + + /** + * Gets a named property from an object or any object in its prototype chain. + * <p> + * Searches the prototype chain for a property named <code>name</code>. + * <p> + * @param obj a JavaScript object + * @param name a property name + * @return the value of a property with name <code>name</code> found in + * <code>obj</code> or any object in its prototype chain, or + * <code>Scriptable.NOT_FOUND</code> if not found + * @since 1.5R2 + */ + public static Object getProperty(Scriptable obj, String name) + { + Scriptable start = obj; + Object result; + do { + result = obj.get(name, start); + if (result != Scriptable.NOT_FOUND) + break; + obj = obj.getPrototype(); + } while (obj != null); + return result; + } + + /** + * Gets an indexed property from an object or any object in its prototype chain. + * <p> + * Searches the prototype chain for a property with integral index + * <code>index</code>. Note that if you wish to look for properties with numerical + * but non-integral indicies, you should use getProperty(Scriptable,String) with + * the string value of the index. + * <p> + * @param obj a JavaScript object + * @param index an integral index + * @return the value of a property with index <code>index</code> found in + * <code>obj</code> or any object in its prototype chain, or + * <code>Scriptable.NOT_FOUND</code> if not found + * @since 1.5R2 + */ + public static Object getProperty(Scriptable obj, int index) + { + Scriptable start = obj; + Object result; + do { + result = obj.get(index, start); + if (result != Scriptable.NOT_FOUND) + break; + obj = obj.getPrototype(); + } while (obj != null); + return result; + } + + /** + * Returns whether a named property is defined in an object or any object + * in its prototype chain. + * <p> + * Searches the prototype chain for a property named <code>name</code>. + * <p> + * @param obj a JavaScript object + * @param name a property name + * @return the true if property was found + * @since 1.5R2 + */ + public static boolean hasProperty(Scriptable obj, String name) + { + return null != getBase(obj, name); + } + + /** + * If hasProperty(obj, name) would return true, then if the property that + * was found is compatible with the new property, this method just returns. + * If the property is not compatible, then an exception is thrown. + * + * A property redefinition is incompatible if the first definition was a + * const declaration or if this one is. They are compatible only if neither + * was const. + */ + public static void redefineProperty(Scriptable obj, String name, + boolean isConst) + { + Scriptable base = getBase(obj, name); + if (base == null) + return; + if (base instanceof ConstProperties) { + ConstProperties cp = (ConstProperties)base; + + if (cp.isConst(name)) + throw Context.reportRuntimeError1("msg.const.redecl", name); + } + if (isConst) + throw Context.reportRuntimeError1("msg.var.redecl", name); + } + /** + * Returns whether an indexed property is defined in an object or any object + * in its prototype chain. + * <p> + * Searches the prototype chain for a property with index <code>index</code>. + * <p> + * @param obj a JavaScript object + * @param index a property index + * @return the true if property was found + * @since 1.5R2 + */ + public static boolean hasProperty(Scriptable obj, int index) + { + return null != getBase(obj, index); + } + + /** + * Puts a named property in an object or in an object in its prototype chain. + * <p> + * Searches for the named property in the prototype chain. If it is found, + * the value of the property in <code>obj</code> is changed through a call + * to {@link Scriptable#put(String, Scriptable, Object)} on the + * prototype passing <code>obj</code> as the <code>start</code> argument. + * This allows the prototype to veto the property setting in case the + * prototype defines the property with [[ReadOnly]] attribute. If the + * property is not found, it is added in <code>obj</code>. + * @param obj a JavaScript object + * @param name a property name + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putProperty(Scriptable obj, String name, Object value) + { + Scriptable base = getBase(obj, name); + if (base == null) + base = obj; + base.put(name, obj, value); + } + + /** + * Puts a named property in an object or in an object in its prototype chain. + * <p> + * Searches for the named property in the prototype chain. If it is found, + * the value of the property in <code>obj</code> is changed through a call + * to {@link Scriptable#put(String, Scriptable, Object)} on the + * prototype passing <code>obj</code> as the <code>start</code> argument. + * This allows the prototype to veto the property setting in case the + * prototype defines the property with [[ReadOnly]] attribute. If the + * property is not found, it is added in <code>obj</code>. + * @param obj a JavaScript object + * @param name a property name + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putConstProperty(Scriptable obj, String name, Object value) + { + Scriptable base = getBase(obj, name); + if (base == null) + base = obj; + if (base instanceof ConstProperties) + ((ConstProperties)base).putConst(name, obj, value); + } + + /** + * Puts an indexed property in an object or in an object in its prototype chain. + * <p> + * Searches for the indexed property in the prototype chain. If it is found, + * the value of the property in <code>obj</code> is changed through a call + * to {@link Scriptable#put(int, Scriptable, Object)} on the prototype + * passing <code>obj</code> as the <code>start</code> argument. This allows + * the prototype to veto the property setting in case the prototype defines + * the property with [[ReadOnly]] attribute. If the property is not found, + * it is added in <code>obj</code>. + * @param obj a JavaScript object + * @param index a property index + * @param value any JavaScript value accepted by Scriptable.put + * @since 1.5R2 + */ + public static void putProperty(Scriptable obj, int index, Object value) + { + Scriptable base = getBase(obj, index); + if (base == null) + base = obj; + base.put(index, obj, value); + } + + /** + * Removes the property from an object or its prototype chain. + * <p> + * Searches for a property with <code>name</code> in obj or + * its prototype chain. If it is found, the object's delete + * method is called. + * @param obj a JavaScript object + * @param name a property name + * @return true if the property doesn't exist or was successfully removed + * @since 1.5R2 + */ + public static boolean deleteProperty(Scriptable obj, String name) + { + Scriptable base = getBase(obj, name); + if (base == null) + return true; + base.delete(name); + return !base.has(name, obj); + } + + /** + * Removes the property from an object or its prototype chain. + * <p> + * Searches for a property with <code>index</code> in obj or + * its prototype chain. If it is found, the object's delete + * method is called. + * @param obj a JavaScript object + * @param index a property index + * @return true if the property doesn't exist or was successfully removed + * @since 1.5R2 + */ + public static boolean deleteProperty(Scriptable obj, int index) + { + Scriptable base = getBase(obj, index); + if (base == null) + return true; + base.delete(index); + return !base.has(index, obj); + } + + /** + * Returns an array of all ids from an object and its prototypes. + * <p> + * @param obj a JavaScript object + * @return an array of all ids from all object in the prototype chain. + * If a given id occurs multiple times in the prototype chain, + * it will occur only once in this list. + * @since 1.5R2 + */ + public static Object[] getPropertyIds(Scriptable obj) + { + if (obj == null) { + return ScriptRuntime.emptyArgs; + } + Object[] result = obj.getIds(); + ObjToIntMap map = null; + for (;;) { + obj = obj.getPrototype(); + if (obj == null) { + break; + } + Object[] ids = obj.getIds(); + if (ids.length == 0) { + continue; + } + if (map == null) { + if (result.length == 0) { + result = ids; + continue; + } + map = new ObjToIntMap(result.length + ids.length); + for (int i = 0; i != result.length; ++i) { + map.intern(result[i]); + } + result = null; // Allow to GC the result + } + for (int i = 0; i != ids.length; ++i) { + map.intern(ids[i]); + } + } + if (map != null) { + result = map.getKeys(); + } + return result; + } + + /** + * Call a method of an object. + * @param obj the JavaScript object + * @param methodName the name of the function property + * @param args the arguments for the call + * + * @see Context#getCurrentContext() + */ + public static Object callMethod(Scriptable obj, String methodName, + Object[] args) + { + return callMethod(null, obj, methodName, args); + } + + /** + * Call a method of an object. + * @param cx the Context object associated with the current thread. + * @param obj the JavaScript object + * @param methodName the name of the function property + * @param args the arguments for the call + */ + public static Object callMethod(Context cx, Scriptable obj, + String methodName, + Object[] args) + { + Object funObj = getProperty(obj, methodName); + if (!(funObj instanceof Function)) { + throw ScriptRuntime.notFunctionError(obj, methodName); + } + Function fun = (Function)funObj; + // XXX: What should be the scope when calling funObj? + // The following favor scope stored in the object on the assumption + // that is more useful especially under dynamic scope setup. + // An alternative is to check for dynamic scope flag + // and use ScriptableObject.getTopLevelScope(fun) if the flag is not + // set. But that require access to Context and messy code + // so for now it is not checked. + Scriptable scope = ScriptableObject.getTopLevelScope(obj); + if (cx != null) { + return fun.call(cx, scope, obj, args); + } else { + return Context.call(null, fun, scope, obj, args); + } + } + + private static Scriptable getBase(Scriptable obj, String name) + { + do { + if (obj.has(name, obj)) + break; + obj = obj.getPrototype(); + } while(obj != null); + return obj; + } + + private static Scriptable getBase(Scriptable obj, int index) + { + do { + if (obj.has(index, obj)) + break; + obj = obj.getPrototype(); + } while(obj != null); + return obj; + } + + /** + * Get arbitrary application-specific value associated with this object. + * @param key key object to select particular value. + * @see #associateValue(Object key, Object value) + */ + public final Object getAssociatedValue(Object key) + { + Hashtable h = associatedValues; + if (h == null) + return null; + return h.get(key); + } + + /** + * Get arbitrary application-specific value associated with the top scope + * of the given scope. + * The method first calls {@link #getTopLevelScope(Scriptable scope)} + * and then searches the prototype chain of the top scope for the first + * object containing the associated value with the given key. + * + * @param scope the starting scope. + * @param key key object to select particular value. + * @see #getAssociatedValue(Object key) + */ + public static Object getTopScopeValue(Scriptable scope, Object key) + { + scope = ScriptableObject.getTopLevelScope(scope); + for (;;) { + if (scope instanceof ScriptableObject) { + ScriptableObject so = (ScriptableObject)scope; + Object value = so.getAssociatedValue(key); + if (value != null) { + return value; + } + } + scope = scope.getPrototype(); + if (scope == null) { + return null; + } + } + } + + /** + * Associate arbitrary application-specific value with this object. + * Value can only be associated with the given object and key only once. + * The method ignores any subsequent attempts to change the already + * associated value. + * <p> The associated values are not serialized. + * @param key key object to select particular value. + * @param value the value to associate + * @return the passed value if the method is called first time for the + * given key or old value for any subsequent calls. + * @see #getAssociatedValue(Object key) + */ + public final Object associateValue(Object key, Object value) + { + if (value == null) throw new IllegalArgumentException(); + Hashtable h = associatedValues; + if (h == null) { + synchronized (this) { + h = associatedValues; + if (h == null) { + h = new Hashtable(); + associatedValues = h; + } + } + } + return Kit.initHash(h, key, value); + } + + private Object getImpl(String name, int index, Scriptable start) + { + Slot slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) { + return Scriptable.NOT_FOUND; + } + if (!(slot instanceof GetterSlot)) { + return slot.value; + } + Object getterObj = ((GetterSlot)slot).getter; + if (getterObj != null) { + if (getterObj instanceof MemberBox) { + MemberBox nativeGetter = (MemberBox)getterObj; + Object getterThis; + Object[] args; + if (nativeGetter.delegateTo == null) { + getterThis = start; + args = ScriptRuntime.emptyArgs; + } else { + getterThis = nativeGetter.delegateTo; + args = new Object[] { start }; + } + return nativeGetter.invoke(getterThis, args); + } else { + Function f = (Function)getterObj; + Context cx = Context.getContext(); + return f.call(cx, f.getParentScope(), start, + ScriptRuntime.emptyArgs); + } + } + Object value = slot.value; + if (value instanceof LazilyLoadedCtor) { + LazilyLoadedCtor initializer = (LazilyLoadedCtor)value; + try { + initializer.init(); + } finally { + value = initializer.getValue(); + slot.value = value; + } + } + return value; + } + + /** + * + * @param name + * @param index + * @param start + * @param value + * @param constFlag EMPTY means normal put. UNINITIALIZED_CONST means + * defineConstProperty. READONLY means const initialization expression. + * @return false if this != start and no slot was found. true if this == start + * or this != start and a READONLY slot was found. + */ + private boolean putImpl(String name, int index, Scriptable start, + Object value, int constFlag) + { + Slot slot; + if (this != start) { + slot = getSlot(name, index, SLOT_QUERY); + if (slot == null) { + return false; + } + } else { + checkNotSealed(name, index); + // either const hoisted declaration or initialization + if (constFlag != EMPTY) { + slot = getSlot(name, index, SLOT_MODIFY_CONST); + int attr = slot.getAttributes(); + if ((attr & READONLY) == 0) + throw Context.reportRuntimeError1("msg.var.redecl", name); + if ((attr & UNINITIALIZED_CONST) != 0) { + slot.value = value; + // clear the bit on const initialization + if (constFlag != UNINITIALIZED_CONST) + slot.setAttributes(attr & ~UNINITIALIZED_CONST); + } + return true; + } + slot = getSlot(name, index, SLOT_MODIFY); + } + if ((slot.getAttributes() & READONLY) != 0) + return true; + if (slot instanceof GetterSlot) { + Object setterObj = ((GetterSlot)slot).setter; + if (setterObj != null) { + Context cx = Context.getContext(); + if (setterObj instanceof MemberBox) { + MemberBox nativeSetter = (MemberBox)setterObj; + Class pTypes[] = nativeSetter.argTypes; + // XXX: cache tag since it is already calculated in + // defineProperty ? + Class valueType = pTypes[pTypes.length - 1]; + int tag = FunctionObject.getTypeTag(valueType); + Object actualArg = FunctionObject.convertArg(cx, start, + value, tag); + Object setterThis; + Object[] args; + if (nativeSetter.delegateTo == null) { + setterThis = start; + args = new Object[] { actualArg }; + } else { + setterThis = nativeSetter.delegateTo; + args = new Object[] { start, actualArg }; + } + nativeSetter.invoke(setterThis, args); + } else { + Function f = (Function)setterObj; + f.call(cx, f.getParentScope(), start, + new Object[] { value }); + } + return true; + } + } + if (this == start) { + slot.value = value; + return true; + } else { + return false; + } + } + + private Slot findAttributeSlot(String name, int index, int accessType) + { + Slot slot = getSlot(name, index, accessType); + if (slot == null) { + String str = (name != null ? name : Integer.toString(index)); + throw Context.reportRuntimeError1("msg.prop.not.found", str); + } + return slot; + } + + /** + * Locate the slot with given name or index. + * + * @param name property name or null if slot holds spare array index. + * @param index index or 0 if slot holds property name. + */ + private Slot getSlot(String name, int index, int accessType) + { + Slot slot; + + // Query last access cache and check that it was not deleted. + lastAccessCheck: + { + slot = lastAccess; + if (name != null) { + if (name != slot.name) + break lastAccessCheck; + // No String.equals here as successful slot search update + // name object with fresh reference of the same string. + } else { + if (slot.name != null || index != slot.indexOrHash) + break lastAccessCheck; + } + + if (slot.wasDeleted != 0) + break lastAccessCheck; + + if (accessType == SLOT_MODIFY_GETTER_SETTER && + !(slot instanceof GetterSlot)) + break lastAccessCheck; + + return slot; + } + + slot = accessSlot(name, index, accessType); + if (slot != null) { + // Update the cache + lastAccess = slot; + } + return slot; + } + + private Slot accessSlot(String name, int index, int accessType) + { + int indexOrHash = (name != null ? name.hashCode() : index); + + if (accessType == SLOT_QUERY || + accessType == SLOT_MODIFY || + accessType == SLOT_MODIFY_CONST || + accessType == SLOT_MODIFY_GETTER_SETTER) + { + // Check the hashtable without using synchronization + + Slot[] slotsLocalRef = slots; // Get stable local reference + if (slotsLocalRef == null) { + if (accessType == SLOT_QUERY) + return null; + } else { + int tableSize = slotsLocalRef.length; + int slotIndex = getSlotIndex(tableSize, indexOrHash); + Slot slot = slotsLocalRef[slotIndex]; + while (slot != null) { + String sname = slot.name; + if (sname != null) { + if (sname == name) + break; + if (name != null && indexOrHash == slot.indexOrHash) { + if (name.equals(sname)) { + // This will avoid calling String.equals when + // slot is accessed with same string object + // next time. + slot.name = name; + break; + } + } + } else if (name == null && + indexOrHash == slot.indexOrHash) { + break; + } + slot = slot.next; + } + if (accessType == SLOT_QUERY) { + return slot; + } else if (accessType == SLOT_MODIFY) { + if (slot != null) + return slot; + } else if (accessType == SLOT_MODIFY_GETTER_SETTER) { + if (slot instanceof GetterSlot) + return slot; + } else if (accessType == SLOT_MODIFY_CONST) { + if (slot != null) + return slot; + } + } + + // A new slot has to be inserted or the old has to be replaced + // by GetterSlot. Time to synchronize. + + synchronized (this) { + // Refresh local ref if another thread triggered grow + slotsLocalRef = slots; + int insertPos; + if (count == 0) { + // Always throw away old slots if any on empty insert + slotsLocalRef = new Slot[5]; + slots = slotsLocalRef; + insertPos = getSlotIndex(slotsLocalRef.length, indexOrHash); + } else { + int tableSize = slotsLocalRef.length; + insertPos = getSlotIndex(tableSize, indexOrHash); + Slot prev = slotsLocalRef[insertPos]; + Slot slot = prev; + while (slot != null) { + if (slot.indexOrHash == indexOrHash && + (slot.name == name || + (name != null && name.equals(slot.name)))) + { + break; + } + prev = slot; + slot = slot.next; + } + + if (slot != null) { + // Another thread just added a slot with same + // name/index before this one entered synchronized + // block. This is a race in application code and + // probably indicates bug there. But for the hashtable + // implementation it is harmless with the only + // complication is the need to replace the added slot + // if we need GetterSlot and the old one is not. + if (accessType == SLOT_MODIFY_GETTER_SETTER && + !(slot instanceof GetterSlot)) + { + GetterSlot newSlot = new GetterSlot(name, indexOrHash, + slot.getAttributes()); + newSlot.value = slot.value; + newSlot.next = slot.next; + if (prev == slot) { + slotsLocalRef[insertPos] = newSlot; + } else { + prev.next = newSlot; + } + slot.wasDeleted = (byte)1; + if (slot == lastAccess) { + lastAccess = REMOVED; + } + slot = newSlot; + } else if (accessType == SLOT_MODIFY_CONST) { + return null; + } + return slot; + } + + // Check if the table is not too full before inserting. + if (4 * (count + 1) > 3 * slotsLocalRef.length) { + slotsLocalRef = new Slot[slotsLocalRef.length * 2 + 1]; + copyTable(slots, slotsLocalRef, count); + slots = slotsLocalRef; + insertPos = getSlotIndex(slotsLocalRef.length, + indexOrHash); + } + } + + Slot newSlot = (accessType == SLOT_MODIFY_GETTER_SETTER + ? new GetterSlot(name, indexOrHash, 0) + : new Slot(name, indexOrHash, 0)); + if (accessType == SLOT_MODIFY_CONST) + newSlot.setAttributes(CONST); + ++count; + addKnownAbsentSlot(slotsLocalRef, newSlot, insertPos); + return newSlot; + } + + } else if (accessType == SLOT_REMOVE) { + synchronized (this) { + Slot[] slotsLocalRef = slots; + if (count != 0) { + int tableSize = slots.length; + int slotIndex = getSlotIndex(tableSize, indexOrHash); + Slot prev = slotsLocalRef[slotIndex]; + Slot slot = prev; + while (slot != null) { + if (slot.indexOrHash == indexOrHash && + (slot.name == name || + (name != null && name.equals(slot.name)))) + { + break; + } + prev = slot; + slot = slot.next; + } + if (slot != null && (slot.getAttributes() & PERMANENT) == 0) { + count--; + if (prev == slot) { + slotsLocalRef[slotIndex] = slot.next; + } else { + prev.next = slot.next; + } + // Mark the slot as removed to handle a case when + // another thread manages to put just removed slot + // into lastAccess cache. + slot.wasDeleted = (byte)1; + if (slot == lastAccess) { + lastAccess = REMOVED; + } + } + } + } + return null; + + } else { + throw Kit.codeBug(); + } + } + + private static int getSlotIndex(int tableSize, int indexOrHash) + { + return (indexOrHash & 0x7fffffff) % tableSize; + } + + // Must be inside synchronized (this) + private static void copyTable(Slot[] slots, Slot[] newSlots, int count) + { + if (count == 0) throw Kit.codeBug(); + + int tableSize = newSlots.length; + int i = slots.length; + for (;;) { + --i; + Slot slot = slots[i]; + while (slot != null) { + int insertPos = getSlotIndex(tableSize, slot.indexOrHash); + Slot next = slot.next; + addKnownAbsentSlot(newSlots, slot, insertPos); + slot.next = null; + slot = next; + if (--count == 0) + return; + } + } + } + + /** + * Add slot with keys that are known to absent from the table. + * This is an optimization to use when inserting into empty table, + * after table growth or during deserialization. + */ + private static void addKnownAbsentSlot(Slot[] slots, Slot slot, int insertPos) + { + if (slots[insertPos] == null) { + slots[insertPos] = slot; + } else { + Slot prev = slots[insertPos]; + while (prev.next != null) { + prev = prev.next; + } + prev.next = slot; + } + } + + Object[] getIds(boolean getAll) { + Slot[] s = slots; + Object[] a = ScriptRuntime.emptyArgs; + if (s == null) + return a; + int c = 0; + for (int i=0; i < s.length; i++) { + Slot slot = s[i]; + while (slot != null) { + if (getAll || (slot.getAttributes() & DONTENUM) == 0) { + if (c == 0) + a = new Object[s.length]; + a[c++] = (slot.name != null ? (Object) slot.name + : new Integer(slot.indexOrHash)); + } + slot = slot.next; + } + } + if (c == a.length) + return a; + Object[] result = new Object[c]; + System.arraycopy(a, 0, result, 0, c); + return result; + } + + private synchronized void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + int objectsCount = count; + if (objectsCount < 0) { + // "this" was sealed + objectsCount = ~objectsCount; + } + if (objectsCount == 0) { + out.writeInt(0); + } else { + out.writeInt(slots.length); + for (int i = 0; i < slots.length; ++i) { + Slot slot = slots[i]; + while (slot != null) { + out.writeObject(slot); + slot = slot.next; + if (--objectsCount == 0) + return; + } + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + lastAccess = REMOVED; + + int tableSize = in.readInt(); + if (tableSize != 0) { + slots = new Slot[tableSize]; + int objectsCount = count; + if (objectsCount < 0) { + // "this" was sealed + objectsCount = ~objectsCount; + } + for (int i = 0; i != objectsCount; ++i) { + Slot slot = (Slot)in.readObject(); + int slotIndex = getSlotIndex(tableSize, slot.indexOrHash); + addKnownAbsentSlot(slots, slot, slotIndex); + } + } + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecureCaller.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecureCaller.java new file mode 100644 index 0000000..bc8ed86 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecureCaller.java @@ -0,0 +1,198 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.SoftReference; +import java.lang.reflect.UndeclaredThrowableException; +import java.net.URL; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.security.SecureClassLoader; +import java.util.Map; +import java.util.WeakHashMap; + +/** + * @author Attila Szegedi + */ +public abstract class SecureCaller +{ + private static final byte[] secureCallerImplBytecode = loadBytecode(); + + // We're storing a CodeSource -> (ClassLoader -> SecureRenderer), since we + // need to have one renderer per class loader. We're using weak hash maps + // and soft references all the way, since we don't want to interfere with + // cleanup of either CodeSource or ClassLoader objects. + private static final Map callers = new WeakHashMap(); + + public abstract Object call(Callable callable, Context cx, + Scriptable scope, Scriptable thisObj, Object[] args); + + /** + * Call the specified callable using a protection domain belonging to the + * specified code source. + */ + static Object callSecurely(final CodeSource codeSource, Callable callable, + Context cx, Scriptable scope, Scriptable thisObj, Object[] args) + { + final Thread thread = Thread.currentThread(); + // Run in doPrivileged as we might be checked for "getClassLoader" + // runtime permission + final ClassLoader classLoader = (ClassLoader)AccessController.doPrivileged( + new PrivilegedAction() { + public Object run() { + return thread.getContextClassLoader(); + } + }); + Map classLoaderMap; + synchronized(callers) + { + classLoaderMap = (Map)callers.get(codeSource); + if(classLoaderMap == null) + { + classLoaderMap = new WeakHashMap(); + callers.put(codeSource, classLoaderMap); + } + } + SecureCaller caller; + synchronized(classLoaderMap) + { + SoftReference ref = (SoftReference)classLoaderMap.get(classLoader); + if(ref != null) + { + caller = (SecureCaller)ref.get(); + } + else + { + caller = null; + } + if(caller == null) + { + try + { + // Run in doPrivileged as we'll be checked for + // "createClassLoader" runtime permission + caller = (SecureCaller)AccessController.doPrivileged( + new PrivilegedExceptionAction() + { + public Object run() throws Exception + { + ClassLoader effectiveClassLoader; + Class thisClass = getClass(); + if(classLoader.loadClass(thisClass.getName()) != thisClass) { + effectiveClassLoader = thisClass.getClassLoader(); + } else { + effectiveClassLoader = classLoader; + } + SecureClassLoaderImpl secCl = + new SecureClassLoaderImpl(effectiveClassLoader); + Class c = secCl.defineAndLinkClass( + SecureCaller.class.getName() + "Impl", + secureCallerImplBytecode, codeSource); + return c.newInstance(); + } + }); + classLoaderMap.put(classLoader, new SoftReference(caller)); + } + catch(PrivilegedActionException ex) + { + throw new UndeclaredThrowableException(ex.getCause()); + } + } + } + return caller.call(callable, cx, scope, thisObj, args); + } + + private static class SecureClassLoaderImpl extends SecureClassLoader + { + SecureClassLoaderImpl(ClassLoader parent) + { + super(parent); + } + + Class defineAndLinkClass(String name, byte[] bytes, CodeSource cs) + { + Class cl = defineClass(name, bytes, 0, bytes.length, cs); + resolveClass(cl); + return cl; + } + } + + private static byte[] loadBytecode() + { + return (byte[])AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return loadBytecodePrivileged(); + } + }); + } + + private static byte[] loadBytecodePrivileged() + { + URL url = SecureCaller.class.getResource("SecureCallerImpl.clazz"); + try + { + InputStream in = url.openStream(); + try + { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + for(;;) + { + int r = in.read(); + if(r == -1) + { + return bout.toByteArray(); + } + bout.write(r); + } + } + finally + { + in.close(); + } + } + catch(IOException e) + { + throw new UndeclaredThrowableException(e); + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityController.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityController.java new file mode 100644 index 0000000..ed85dbf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityController.java @@ -0,0 +1,211 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This class describes the support needed to implement security. + * <p> + * Three main pieces of functionality are required to implement + * security for JavaScript. First, it must be possible to define + * classes with an associated security domain. (This security + * domain may be any object incorporating notion of access + * restrictions that has meaning to an embedding; for a client-side + * JavaScript embedding this would typically be + * java.security.ProtectionDomain or similar object depending on an + * origin URL and/or a digital certificate.) + * Next it must be possible to get a security domain object that + * allows a particular action only if all security domains + * associated with code on the current Java stack allows it. And + * finally, it must be possible to execute script code with + * associated security domain injected into Java stack. + * <p> + * These three pieces of functionality are encapsulated in the + * SecurityController class. + * + * @see org.mozilla.javascript.Context#setSecurityController(SecurityController) + * @see java.lang.ClassLoader + * @since 1.5 Release 4 + */ +public abstract class SecurityController +{ + private static SecurityController global; + +// The method must NOT be public or protected + static SecurityController global() + { + return global; + } + + /** + * Check if global {@link SecurityController} was already installed. + * @see #initGlobal(SecurityController controller) + */ + public static boolean hasGlobal() + { + return global != null; + } + + /** + * Initialize global controller that will be used for all + * security-related operations. The global controller takes precedence + * over already installed {@link Context}-specific controllers and cause + * any subsequent call to + * {@link Context#setSecurityController(SecurityController)} + * to throw an exception. + * <p> + * The method can only be called once. + * + * @see #hasGlobal() + */ + public static void initGlobal(SecurityController controller) + { + if (controller == null) throw new IllegalArgumentException(); + if (global != null) { + throw new SecurityException("Cannot overwrite already installed global SecurityController"); + } + global = controller; + } + + /** + * Get class loader-like object that can be used + * to define classes with the given security context. + * @param parentLoader parent class loader to delegate search for classes + * not defined by the class loader itself + * @param securityDomain some object specifying the security + * context of the code that is defined by the returned class loader. + */ + public abstract GeneratedClassLoader createClassLoader( + ClassLoader parentLoader, Object securityDomain); + + /** + * Create {@link GeneratedClassLoader} with restrictions imposed by + * staticDomain and all current stack frames. + * The method uses the SecurityController instance associated with the + * current {@link Context} to construct proper dynamic domain and create + * corresponding class loader. + * <par> + * If no SecurityController is associated with the current {@link Context} , + * the method calls {@link Context#createClassLoader(ClassLoader parent)}. + * + * @param parent parent class loader. If null, + * {@link Context#getApplicationClassLoader()} will be used. + * @param staticDomain static security domain. + */ + public static GeneratedClassLoader createLoader( + ClassLoader parent, Object staticDomain) + { + Context cx = Context.getContext(); + if (parent == null) { + parent = cx.getApplicationClassLoader(); + } + SecurityController sc = cx.getSecurityController(); + GeneratedClassLoader loader; + if (sc == null) { + loader = cx.createClassLoader(parent); + } else { + Object dynamicDomain = sc.getDynamicSecurityDomain(staticDomain); + loader = sc.createClassLoader(parent, dynamicDomain); + } + return loader; + } + + public static Class getStaticSecurityDomainClass() { + SecurityController sc = Context.getContext().getSecurityController(); + return sc == null ? null : sc.getStaticSecurityDomainClassInternal(); + } + + public Class getStaticSecurityDomainClassInternal() + { + return null; + } + + /** + * Get dynamic security domain that allows an action only if it is allowed + * by the current Java stack and <i>securityDomain</i>. If + * <i>securityDomain</i> is null, return domain representing permissions + * allowed by the current stack. + */ + public abstract Object getDynamicSecurityDomain(Object securityDomain); + + /** + * Call {@link + * Callable#call(Context cx, Scriptable scope, Scriptable thisObj, + * Object[] args)} + * of <i>callable</i> under restricted security domain where an action is + * allowed only if it is allowed according to the Java stack on the + * moment of the <i>execWithDomain</i> call and <i>securityDomain</i>. + * Any call to {@link #getDynamicSecurityDomain(Object)} during + * execution of <tt>callable.call(cx, scope, thisObj, args)</tt> + * should return a domain incorporate restrictions imposed by + * <i>securityDomain</i> and Java stack on the moment of callWithDomain + * invocation. + * <p> + * The method should always be overridden, it is not declared abstract + * for compatibility reasons. + */ + public Object callWithDomain(Object securityDomain, Context cx, + final Callable callable, Scriptable scope, + final Scriptable thisObj, final Object[] args) + { + return execWithDomain(cx, scope, new Script() + { + public Object exec(Context cx, Scriptable scope) + { + return callable.call(cx, scope, thisObj, args); + } + + }, securityDomain); + } + + /** + * @deprecated The application should not override this method and instead + * override + * {@link #callWithDomain(Object securityDomain, Context cx, Callable callable, Scriptable scope, Scriptable thisObj, Object[] args)}. + */ + public Object execWithDomain(Context cx, Scriptable scope, + Script script, Object securityDomain) + { + throw new IllegalStateException("callWithDomain should be overridden"); + } + + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityUtilities.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityUtilities.java new file mode 100644 index 0000000..275ad92 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SecurityUtilities.java @@ -0,0 +1,80 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * + * 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.security.AccessController; +import java.security.PrivilegedAction; +import java.security.ProtectionDomain; + +/** + * @author Attila Szegedi + */ +public class SecurityUtilities +{ + /** + * Retrieves a system property within a privileged block. Use it only when + * the property is used from within Rhino code and is not passed out of it. + * @param name the name of the system property + * @return the value of the system property + */ + public static String getSystemProperty(final String name) + { + return (String)AccessController.doPrivileged( + new PrivilegedAction() + { + public Object run() + { + return System.getProperty(name); + } + }); + } + + public static ProtectionDomain getProtectionDomain(final Class clazz) + { + return (ProtectionDomain)AccessController.doPrivileged( + new PrivilegedAction() + { + public Object run() + { + return clazz.getProtectionDomain(); + } + }); + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SpecialRef.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SpecialRef.java new file mode 100644 index 0000000..b037eaf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/SpecialRef.java @@ -0,0 +1,151 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov, igor@fastmail.fm + * + * 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; + +class SpecialRef extends Ref +{ + static final long serialVersionUID = -7521596632456797847L; + + private static final int SPECIAL_NONE = 0; + private static final int SPECIAL_PROTO = 1; + private static final int SPECIAL_PARENT = 2; + + private Scriptable target; + private int type; + private String name; + + private SpecialRef(Scriptable target, int type, String name) + { + this.target = target; + this.type = type; + this.name = name; + } + + static Ref createSpecial(Context cx, Object object, String name) + { + Scriptable target = ScriptRuntime.toObjectOrNull(cx, object); + if (target == null) { + throw ScriptRuntime.undefReadError(object, name); + } + + int type; + if (name.equals("__proto__")) { + type = SPECIAL_PROTO; + } else if (name.equals("__parent__")) { + type = SPECIAL_PARENT; + } else { + throw new IllegalArgumentException(name); + } + + if (!cx.hasFeature(Context.FEATURE_PARENT_PROTO_PROPERTIES)) { + // Clear special after checking for valid name! + type = SPECIAL_NONE; + } + + return new SpecialRef(target, type, name); + } + + public Object get(Context cx) + { + switch (type) { + case SPECIAL_NONE: + return ScriptRuntime.getObjectProp(target, name, cx); + case SPECIAL_PROTO: + return target.getPrototype(); + case SPECIAL_PARENT: + return target.getParentScope(); + default: + throw Kit.codeBug(); + } + } + + public Object set(Context cx, Object value) + { + switch (type) { + case SPECIAL_NONE: + return ScriptRuntime.setObjectProp(target, name, value, cx); + case SPECIAL_PROTO: + case SPECIAL_PARENT: + { + Scriptable obj = ScriptRuntime.toObjectOrNull(cx, value); + if (obj != null) { + // Check that obj does not contain on its prototype/scope + // chain to prevent cycles + Scriptable search = obj; + do { + if (search == target) { + throw Context.reportRuntimeError1( + "msg.cyclic.value", name); + } + if (type == SPECIAL_PROTO) { + search = search.getPrototype(); + } else { + search = search.getParentScope(); + } + } while (search != null); + } + if (type == SPECIAL_PROTO) { + target.setPrototype(obj); + } else { + target.setParentScope(obj); + } + return obj; + } + default: + throw Kit.codeBug(); + } + } + + public boolean has(Context cx) + { + if (type == SPECIAL_NONE) { + return ScriptRuntime.hasObjectElem(target, name, cx); + } + return true; + } + + public boolean delete(Context cx) + { + if (type == SPECIAL_NONE) { + return ScriptRuntime.deleteObjectElem(target, name, cx); + } + return false; + } +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Synchronizer.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Synchronizer.java new file mode 100644 index 0000000..f2fca52 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Synchronizer.java @@ -0,0 +1,81 @@ +/* -*- 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 Delegator.java, released + * Sep 27, 2000. + * + * The Initial Developer of the Original Code is + * Matthias Radestock. <matthias@sorted.org>. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * This class provides support for implementing Java-style synchronized + * methods in Javascript. + * + * Synchronized functions are created from ordinary Javascript + * functions by the <code>Synchronizer</code> constructor, e.g. + * <code>new Packages.org.mozilla.javascript.Synchronizer(fun)</code>. + * The resulting object is a function that establishes an exclusive + * lock on the <code>this</code> object of its invocation. + * + * The Rhino shell provides a short-cut for the creation of + * synchronized methods: <code>sync(fun)</code> has the same effect as + * calling the above constructor. + * + * @see org.mozilla.javascript.Delegator + * @author Matthias Radestock + */ + +public class Synchronizer extends Delegator { + + /** + * Create a new synchronized function from an existing one. + * + * @param obj the existing function + */ + public Synchronizer(Scriptable obj) { + super(obj); + } + + /** + * @see org.mozilla.javascript.Function#call + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + synchronized(thisObj instanceof Wrapper ? ((Wrapper)thisObj).unwrap() : thisObj) { + return ((Function)obj).call(cx,scope,thisObj,args); + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Token.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Token.java new file mode 100644 index 0000000..be96487 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Token.java @@ -0,0 +1,436 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * Mike McCabe + * Igor Bukanov + * Bob Jervis + * Milen Nankov + * + * 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; + +/** + * This class implements the JavaScript scanner. + * + * It is based on the C source files jsscan.c and jsscan.h + * in the jsref package. + * + * @see org.mozilla.javascript.Parser + * + * @author Mike McCabe + * @author Brendan Eich + */ + +public class Token +{ + + // debug flags + public static final boolean printTrees = + /*APPJET: some info not generated with this off; we disable + actual printing in Interpreter and CodeGen */true; + static final boolean printICode = false; + static final boolean printNames = printTrees || printICode; + + /** + * Token types. These values correspond to JSTokenType values in + * jsscan.c. + */ + + public final static int + // start enum + ERROR = -1, // well-known as the only code < EOF + EOF = 0, // end of file token - (not EOF_CHAR) + EOL = 1, // end of line + + // Interpreter reuses the following as bytecodes + FIRST_BYTECODE_TOKEN = 2, + + ENTERWITH = 2, + LEAVEWITH = 3, + RETURN = 4, + GOTO = 5, + IFEQ = 6, + IFNE = 7, + SETNAME = 8, + BITOR = 9, + BITXOR = 10, + BITAND = 11, + EQ = 12, + NE = 13, + LT = 14, + LE = 15, + GT = 16, + GE = 17, + LSH = 18, + RSH = 19, + URSH = 20, + ADD = 21, + SUB = 22, + MUL = 23, + DIV = 24, + MOD = 25, + NOT = 26, + BITNOT = 27, + POS = 28, + NEG = 29, + NEW = 30, + DELPROP = 31, + TYPEOF = 32, + GETPROP = 33, + GETPROPNOWARN = 34, + SETPROP = 35, + GETELEM = 36, + SETELEM = 37, + CALL = 38, + NAME = 39, + NUMBER = 40, + STRING = 41, + NULL = 42, + THIS = 43, + FALSE = 44, + TRUE = 45, + SHEQ = 46, // shallow equality (===) + SHNE = 47, // shallow inequality (!==) + REGEXP = 48, + BINDNAME = 49, + THROW = 50, + RETHROW = 51, // rethrow caught exception: catch (e if ) use it + IN = 52, + INSTANCEOF = 53, + LOCAL_LOAD = 54, + GETVAR = 55, + SETVAR = 56, + CATCH_SCOPE = 57, + ENUM_INIT_KEYS = 58, + ENUM_INIT_VALUES = 59, + ENUM_INIT_ARRAY= 60, + ENUM_NEXT = 61, + ENUM_ID = 62, + THISFN = 63, + RETURN_RESULT = 64, // to return previously stored return result + ARRAYLIT = 65, // array literal + OBJECTLIT = 66, // object literal + GET_REF = 67, // *reference + SET_REF = 68, // *reference = something + DEL_REF = 69, // delete reference + REF_CALL = 70, // f(args) = something or f(args)++ + REF_SPECIAL = 71, // reference for special properties like __proto + YIELD = 72, // JS 1.7 yield pseudo keyword + + // For XML support: + DEFAULTNAMESPACE = 73, // default xml namespace = + ESCXMLATTR = 74, + ESCXMLTEXT = 75, + REF_MEMBER = 76, // Reference for x.@y, x..y etc. + REF_NS_MEMBER = 77, // Reference for x.ns::y, x..ns::y etc. + REF_NAME = 78, // Reference for @y, @[y] etc. + REF_NS_NAME = 79; // Reference for ns::y, @ns::y@[y] etc. + + // End of interpreter bytecodes + public final static int + LAST_BYTECODE_TOKEN = REF_NS_NAME, + + TRY = 80, + SEMI = 81, // semicolon + LB = 82, // left and right brackets + RB = 83, + LC = 84, // left and right curlies (braces) + RC = 85, + LP = 86, // left and right parentheses + RP = 87, + COMMA = 88, // comma operator + + ASSIGN = 89, // simple assignment (=) + ASSIGN_BITOR = 90, // |= + ASSIGN_BITXOR = 91, // ^= + ASSIGN_BITAND = 92, // |= + ASSIGN_LSH = 93, // <<= + ASSIGN_RSH = 94, // >>= + ASSIGN_URSH = 95, // >>>= + ASSIGN_ADD = 96, // += + ASSIGN_SUB = 97, // -= + ASSIGN_MUL = 98, // *= + ASSIGN_DIV = 99, // /= + ASSIGN_MOD = 100; // %= + + public final static int + FIRST_ASSIGN = ASSIGN, + LAST_ASSIGN = ASSIGN_MOD, + + HOOK = 101, // conditional (?:) + COLON = 102, + OR = 103, // logical or (||) + AND = 104, // logical and (&&) + INC = 105, // increment/decrement (++ --) + DEC = 106, + DOT = 107, // member operator (.) + FUNCTION = 108, // function keyword + EXPORT = 109, // export keyword + IMPORT = 110, // import keyword + IF = 111, // if keyword + ELSE = 112, // else keyword + SWITCH = 113, // switch keyword + CASE = 114, // case keyword + DEFAULT = 115, // default keyword + WHILE = 116, // while keyword + DO = 117, // do keyword + FOR = 118, // for keyword + BREAK = 119, // break keyword + CONTINUE = 120, // continue keyword + VAR = 121, // var keyword + WITH = 122, // with keyword + CATCH = 123, // catch keyword + FINALLY = 124, // finally keyword + VOID = 125, // void keyword + RESERVED = 126, // reserved keywords + + EMPTY = 127, + + /* types used for the parse tree - these never get returned + * by the scanner. + */ + + BLOCK = 128, // statement block + LABEL = 129, // label + TARGET = 130, + LOOP = 131, + EXPR_VOID = 132, // expression statement in functions + EXPR_RESULT = 133, // expression statement in scripts + JSR = 134, + SCRIPT = 135, // top-level node for entire script + TYPEOFNAME = 136, // for typeof(simple-name) + USE_STACK = 137, + SETPROP_OP = 138, // x.y op= something + SETELEM_OP = 139, // x[y] op= something + LOCAL_BLOCK = 140, + SET_REF_OP = 141, // *reference op= something + + // For XML support: + DOTDOT = 142, // member operator (..) + COLONCOLON = 143, // namespace::name + XML = 144, // XML type + DOTQUERY = 145, // .() -- e.g., x.emps.emp.(name == "terry") + XMLATTR = 146, // @ + XMLEND = 147, + + // Optimizer-only-tokens + TO_OBJECT = 148, + TO_DOUBLE = 149, + + GET = 150, // JS 1.5 get pseudo keyword + SET = 151, // JS 1.5 set pseudo keyword + LET = 152, // JS 1.7 let pseudo keyword + CONST = 153, + SETCONST = 154, + SETCONSTVAR = 155, + ARRAYCOMP = 156, // array comprehension + LETEXPR = 157, + WITHEXPR = 158, + DEBUGGER = 159, + LAST_TOKEN = 159; + + public static String name(int token) + { + if (!printNames) { + return String.valueOf(token); + } + switch (token) { + case ERROR: return "ERROR"; + case EOF: return "EOF"; + case EOL: return "EOL"; + case ENTERWITH: return "ENTERWITH"; + case LEAVEWITH: return "LEAVEWITH"; + case RETURN: return "RETURN"; + case GOTO: return "GOTO"; + case IFEQ: return "IFEQ"; + case IFNE: return "IFNE"; + case SETNAME: return "SETNAME"; + case BITOR: return "BITOR"; + case BITXOR: return "BITXOR"; + case BITAND: return "BITAND"; + case EQ: return "EQ"; + case NE: return "NE"; + case LT: return "LT"; + case LE: return "LE"; + case GT: return "GT"; + case GE: return "GE"; + case LSH: return "LSH"; + case RSH: return "RSH"; + case URSH: return "URSH"; + case ADD: return "ADD"; + case SUB: return "SUB"; + case MUL: return "MUL"; + case DIV: return "DIV"; + case MOD: return "MOD"; + case NOT: return "NOT"; + case BITNOT: return "BITNOT"; + case POS: return "POS"; + case NEG: return "NEG"; + case NEW: return "NEW"; + case DELPROP: return "DELPROP"; + case TYPEOF: return "TYPEOF"; + case GETPROP: return "GETPROP"; + case GETPROPNOWARN: return "GETPROPNOWARN"; + case SETPROP: return "SETPROP"; + case GETELEM: return "GETELEM"; + case SETELEM: return "SETELEM"; + case CALL: return "CALL"; + case NAME: return "NAME"; + case NUMBER: return "NUMBER"; + case STRING: return "STRING"; + case NULL: return "NULL"; + case THIS: return "THIS"; + case FALSE: return "FALSE"; + case TRUE: return "TRUE"; + case SHEQ: return "SHEQ"; + case SHNE: return "SHNE"; + case REGEXP: return "OBJECT"; + case BINDNAME: return "BINDNAME"; + case THROW: return "THROW"; + case RETHROW: return "RETHROW"; + case IN: return "IN"; + case INSTANCEOF: return "INSTANCEOF"; + case LOCAL_LOAD: return "LOCAL_LOAD"; + case GETVAR: return "GETVAR"; + case SETVAR: return "SETVAR"; + case CATCH_SCOPE: return "CATCH_SCOPE"; + case ENUM_INIT_KEYS: return "ENUM_INIT_KEYS"; + case ENUM_INIT_VALUES:return "ENUM_INIT_VALUES"; + case ENUM_INIT_ARRAY: return "ENUM_INIT_ARRAY"; + case ENUM_NEXT: return "ENUM_NEXT"; + case ENUM_ID: return "ENUM_ID"; + case THISFN: return "THISFN"; + case RETURN_RESULT: return "RETURN_RESULT"; + case ARRAYLIT: return "ARRAYLIT"; + case OBJECTLIT: return "OBJECTLIT"; + case GET_REF: return "GET_REF"; + case SET_REF: return "SET_REF"; + case DEL_REF: return "DEL_REF"; + case REF_CALL: return "REF_CALL"; + case REF_SPECIAL: return "REF_SPECIAL"; + case DEFAULTNAMESPACE:return "DEFAULTNAMESPACE"; + case ESCXMLTEXT: return "ESCXMLTEXT"; + case ESCXMLATTR: return "ESCXMLATTR"; + case REF_MEMBER: return "REF_MEMBER"; + case REF_NS_MEMBER: return "REF_NS_MEMBER"; + case REF_NAME: return "REF_NAME"; + case REF_NS_NAME: return "REF_NS_NAME"; + case TRY: return "TRY"; + case SEMI: return "SEMI"; + case LB: return "LB"; + case RB: return "RB"; + case LC: return "LC"; + case RC: return "RC"; + case LP: return "LP"; + case RP: return "RP"; + case COMMA: return "COMMA"; + case ASSIGN: return "ASSIGN"; + case ASSIGN_BITOR: return "ASSIGN_BITOR"; + case ASSIGN_BITXOR: return "ASSIGN_BITXOR"; + case ASSIGN_BITAND: return "ASSIGN_BITAND"; + case ASSIGN_LSH: return "ASSIGN_LSH"; + case ASSIGN_RSH: return "ASSIGN_RSH"; + case ASSIGN_URSH: return "ASSIGN_URSH"; + case ASSIGN_ADD: return "ASSIGN_ADD"; + case ASSIGN_SUB: return "ASSIGN_SUB"; + case ASSIGN_MUL: return "ASSIGN_MUL"; + case ASSIGN_DIV: return "ASSIGN_DIV"; + case ASSIGN_MOD: return "ASSIGN_MOD"; + case HOOK: return "HOOK"; + case COLON: return "COLON"; + case OR: return "OR"; + case AND: return "AND"; + case INC: return "INC"; + case DEC: return "DEC"; + case DOT: return "DOT"; + case FUNCTION: return "FUNCTION"; + case EXPORT: return "EXPORT"; + case IMPORT: return "IMPORT"; + case IF: return "IF"; + case ELSE: return "ELSE"; + case SWITCH: return "SWITCH"; + case CASE: return "CASE"; + case DEFAULT: return "DEFAULT"; + case WHILE: return "WHILE"; + case DO: return "DO"; + case FOR: return "FOR"; + case BREAK: return "BREAK"; + case CONTINUE: return "CONTINUE"; + case VAR: return "VAR"; + case WITH: return "WITH"; + case CATCH: return "CATCH"; + case FINALLY: return "FINALLY"; + case VOID: return "VOID"; + case RESERVED: return "RESERVED"; + case EMPTY: return "EMPTY"; + case BLOCK: return "BLOCK"; + case LABEL: return "LABEL"; + case TARGET: return "TARGET"; + case LOOP: return "LOOP"; + case EXPR_VOID: return "EXPR_VOID"; + case EXPR_RESULT: return "EXPR_RESULT"; + case JSR: return "JSR"; + case SCRIPT: return "SCRIPT"; + case TYPEOFNAME: return "TYPEOFNAME"; + case USE_STACK: return "USE_STACK"; + case SETPROP_OP: return "SETPROP_OP"; + case SETELEM_OP: return "SETELEM_OP"; + case LOCAL_BLOCK: return "LOCAL_BLOCK"; + case SET_REF_OP: return "SET_REF_OP"; + case DOTDOT: return "DOTDOT"; + case COLONCOLON: return "COLONCOLON"; + case XML: return "XML"; + case DOTQUERY: return "DOTQUERY"; + case XMLATTR: return "XMLATTR"; + case XMLEND: return "XMLEND"; + case TO_OBJECT: return "TO_OBJECT"; + case TO_DOUBLE: return "TO_DOUBLE"; + case GET: return "GET"; + case SET: return "SET"; + case LET: return "LET"; + case YIELD: return "YIELD"; + case CONST: return "CONST"; + case SETCONST: return "SETCONST"; + case ARRAYCOMP: return "ARRAYCOMP"; + case WITHEXPR: return "WITHEXPR"; + case LETEXPR: return "LETEXPR"; + case DEBUGGER: return "DEBUGGER"; + } + + // Token without name + throw new IllegalStateException(String.valueOf(token)); + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/TokenStream.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/TokenStream.java new file mode 100644 index 0000000..c8c3045 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/TokenStream.java @@ -0,0 +1,1500 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * Mike McCabe + * Igor Bukanov + * Ethan Hugg + * Bob Jervis + * Terry Lucas + * Milen Nankov + * + * 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.io.*; + +/** + * This class implements the JavaScript scanner. + * + * It is based on the C source files jsscan.c and jsscan.h + * in the jsref package. + * + * @see org.mozilla.javascript.Parser + * + * @author Mike McCabe + * @author Brendan Eich + */ + +class TokenStream +{ + /* + * For chars - because we need something out-of-range + * to check. (And checking EOF by exception is annoying.) + * Note distinction from EOF token type! + */ + private final static int + EOF_CHAR = -1; + + TokenStream(Parser parser, Reader sourceReader, String sourceString, + int lineno) + { + this.parser = parser; + this.lineno = lineno; + if (sourceReader != null) { + if (sourceString != null) Kit.codeBug(); + this.sourceReader = sourceReader; + this.sourceBuffer = new char[512]; + this.sourceEnd = 0; + } else { + if (sourceString == null) Kit.codeBug(); + this.sourceString = sourceString; + this.sourceEnd = sourceString.length(); + } + this.sourceCursor = 0; + } + + /* This function uses the cached op, string and number fields in + * TokenStream; if getToken has been called since the passed token + * was scanned, the op or string printed may be incorrect. + */ + String tokenToString(int token) + { + if (Token.printTrees) { + String name = Token.name(token); + + switch (token) { + case Token.STRING: + case Token.REGEXP: + case Token.NAME: + return name + " `" + this.string + "'"; + + case Token.NUMBER: + return "NUMBER " + this.number; + } + + return name; + } + return ""; + } + + static boolean isKeyword(String s) + { + return Token.EOF != stringToKeyword(s); + } + + private static int stringToKeyword(String name) + { +// #string_id_map# +// The following assumes that Token.EOF == 0 + final int + Id_break = Token.BREAK, + Id_case = Token.CASE, + Id_continue = Token.CONTINUE, + Id_default = Token.DEFAULT, + Id_delete = Token.DELPROP, + Id_do = Token.DO, + Id_else = Token.ELSE, + Id_export = Token.EXPORT, + Id_false = Token.FALSE, + Id_for = Token.FOR, + Id_function = Token.FUNCTION, + Id_if = Token.IF, + Id_in = Token.IN, + Id_let = Token.LET, + Id_new = Token.NEW, + Id_null = Token.NULL, + Id_return = Token.RETURN, + Id_switch = Token.SWITCH, + Id_this = Token.THIS, + Id_true = Token.TRUE, + Id_typeof = Token.TYPEOF, + Id_var = Token.VAR, + Id_void = Token.VOID, + Id_while = Token.WHILE, + Id_with = Token.WITH, + Id_yield = Token.YIELD, + + // the following are #ifdef RESERVE_JAVA_KEYWORDS in jsscan.c + Id_abstract = Token.RESERVED, + Id_boolean = Token.RESERVED, + Id_byte = Token.RESERVED, + Id_catch = Token.CATCH, + Id_char = Token.RESERVED, + Id_class = Token.RESERVED, + Id_const = Token.CONST, + Id_debugger = Token.DEBUGGER, + Id_double = Token.RESERVED, + Id_enum = Token.RESERVED, + Id_extends = Token.RESERVED, + Id_final = Token.RESERVED, + Id_finally = Token.FINALLY, + Id_float = Token.RESERVED, + Id_goto = Token.RESERVED, + Id_implements = Token.RESERVED, + Id_import = Token.IMPORT, + Id_instanceof = Token.INSTANCEOF, + Id_int = Token.RESERVED, + Id_interface = Token.RESERVED, + Id_long = Token.RESERVED, + Id_native = Token.RESERVED, + Id_package = Token.RESERVED, + Id_private = Token.RESERVED, + Id_protected = Token.RESERVED, + Id_public = Token.RESERVED, + Id_short = Token.RESERVED, + Id_static = Token.RESERVED, + Id_super = Token.RESERVED, + Id_synchronized = Token.RESERVED, + Id_throw = Token.THROW, + Id_throws = Token.RESERVED, + Id_transient = Token.RESERVED, + Id_try = Token.TRY, + Id_volatile = Token.RESERVED; + + int id; + String s = name; +// #generated# Last update: 2007-04-18 13:53:30 PDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 2: c=s.charAt(1); + if (c=='f') { if (s.charAt(0)=='i') {id=Id_if; break L0;} } + else if (c=='n') { if (s.charAt(0)=='i') {id=Id_in; break L0;} } + else if (c=='o') { if (s.charAt(0)=='d') {id=Id_do; break L0;} } + break L; + case 3: switch (s.charAt(0)) { + case 'f': if (s.charAt(2)=='r' && s.charAt(1)=='o') {id=Id_for; break L0;} break L; + case 'i': if (s.charAt(2)=='t' && s.charAt(1)=='n') {id=Id_int; break L0;} break L; + case 'l': if (s.charAt(2)=='t' && s.charAt(1)=='e') {id=Id_let; break L0;} break L; + case 'n': if (s.charAt(2)=='w' && s.charAt(1)=='e') {id=Id_new; break L0;} break L; + case 't': if (s.charAt(2)=='y' && s.charAt(1)=='r') {id=Id_try; break L0;} break L; + case 'v': if (s.charAt(2)=='r' && s.charAt(1)=='a') {id=Id_var; break L0;} break L; + } break L; + case 4: switch (s.charAt(0)) { + case 'b': X="byte";id=Id_byte; break L; + case 'c': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='a') {id=Id_case; break L0;} } + else if (c=='r') { if (s.charAt(2)=='a' && s.charAt(1)=='h') {id=Id_char; break L0;} } + break L; + case 'e': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='s' && s.charAt(1)=='l') {id=Id_else; break L0;} } + else if (c=='m') { if (s.charAt(2)=='u' && s.charAt(1)=='n') {id=Id_enum; break L0;} } + break L; + case 'g': X="goto";id=Id_goto; break L; + case 'l': X="long";id=Id_long; break L; + case 'n': X="null";id=Id_null; break L; + case 't': c=s.charAt(3); + if (c=='e') { if (s.charAt(2)=='u' && s.charAt(1)=='r') {id=Id_true; break L0;} } + else if (c=='s') { if (s.charAt(2)=='i' && s.charAt(1)=='h') {id=Id_this; break L0;} } + break L; + case 'v': X="void";id=Id_void; break L; + case 'w': X="with";id=Id_with; break L; + } break L; + case 5: switch (s.charAt(2)) { + case 'a': X="class";id=Id_class; break L; + case 'e': c=s.charAt(0); + if (c=='b') { X="break";id=Id_break; } + else if (c=='y') { X="yield";id=Id_yield; } + break L; + case 'i': X="while";id=Id_while; break L; + case 'l': X="false";id=Id_false; break L; + case 'n': c=s.charAt(0); + if (c=='c') { X="const";id=Id_const; } + else if (c=='f') { X="final";id=Id_final; } + break L; + case 'o': c=s.charAt(0); + if (c=='f') { X="float";id=Id_float; } + else if (c=='s') { X="short";id=Id_short; } + break L; + case 'p': X="super";id=Id_super; break L; + case 'r': X="throw";id=Id_throw; break L; + case 't': X="catch";id=Id_catch; break L; + } break L; + case 6: switch (s.charAt(1)) { + case 'a': X="native";id=Id_native; break L; + case 'e': c=s.charAt(0); + if (c=='d') { X="delete";id=Id_delete; } + else if (c=='r') { X="return";id=Id_return; } + break L; + case 'h': X="throws";id=Id_throws; break L; + /*APPJET*//* case 'm': X="import";id=Id_import; break L;*/ + case 'o': X="double";id=Id_double; break L; + case 't': X="static";id=Id_static; break L; + case 'u': X="public";id=Id_public; break L; + case 'w': X="switch";id=Id_switch; break L; + case 'x': X="export";id=Id_export; break L; + case 'y': X="typeof";id=Id_typeof; break L; + } break L; + case 7: switch (s.charAt(1)) { + case 'a': X="package";id=Id_package; break L; + case 'e': X="default";id=Id_default; break L; + case 'i': X="finally";id=Id_finally; break L; + case 'o': X="boolean";id=Id_boolean; break L; + case 'r': X="private";id=Id_private; break L; + case 'x': X="extends";id=Id_extends; break L; + } break L; + case 8: switch (s.charAt(0)) { + case 'a': X="abstract";id=Id_abstract; break L; + case 'c': X="continue";id=Id_continue; break L; + case 'd': X="debugger";id=Id_debugger; break L; + case 'f': X="function";id=Id_function; break L; + case 'v': X="volatile";id=Id_volatile; break L; + } break L; + case 9: c=s.charAt(0); + if (c=='i') { X="interface";id=Id_interface; } + else if (c=='p') { X="protected";id=Id_protected; } + else if (c=='t') { X="transient";id=Id_transient; } + break L; + case 10: c=s.charAt(1); + if (c=='m') { X="implements";id=Id_implements; } + else if (c=='n') { X="instanceof";id=Id_instanceof; } + break L; + case 12: X="synchronized";id=Id_synchronized; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# +// #/string_id_map# + if (id == 0) { return Token.EOF; } + return id & 0xff; + } + + final int getLineno() { return lineno; } + + final String getString() { return string; } + + final double getNumber() { return number; } + + final boolean eof() { return hitEOF; } + + final int getToken() throws IOException + { + int c; + + retry: + for (;;) { + // Eat whitespace, possibly sensitive to newlines. + for (;;) { + c = getChar(); + if (c == EOF_CHAR) { + return Token.EOF; + } else if (c == '\n') { + dirtyLine = false; + return Token.EOL; + } else if (!isJSSpace(c)) { + if (c != '-') { + dirtyLine = true; + } + break; + } + } + + if (c == '@') return Token.XMLATTR; + + // identifier/keyword/instanceof? + // watch out for starting with a <backslash> + boolean identifierStart; + boolean isUnicodeEscapeStart = false; + if (c == '\\') { + c = getChar(); + if (c == 'u') { + identifierStart = true; + isUnicodeEscapeStart = true; + stringBufferTop = 0; + } else { + identifierStart = false; + ungetChar(c); + c = '\\'; + } + } else { + identifierStart = Character.isJavaIdentifierStart((char)c); + if (identifierStart) { + stringBufferTop = 0; + addToString(c); + } + } + + if (identifierStart) { + boolean containsEscape = isUnicodeEscapeStart; + for (;;) { + if (isUnicodeEscapeStart) { + // strictly speaking we should probably push-back + // all the bad characters if the <backslash>uXXXX + // sequence is malformed. But since there isn't a + // correct context(is there?) for a bad Unicode + // escape sequence in an identifier, we can report + // an error here. + int escapeVal = 0; + for (int i = 0; i != 4; ++i) { + c = getChar(); + escapeVal = Kit.xDigitToInt(c, escapeVal); + // Next check takes care about c < 0 and bad escape + if (escapeVal < 0) { break; } + } + if (escapeVal < 0) { + parser.addError("msg.invalid.escape"); + return Token.ERROR; + } + addToString(escapeVal); + isUnicodeEscapeStart = false; + } else { + c = getChar(); + if (c == '\\') { + c = getChar(); + if (c == 'u') { + isUnicodeEscapeStart = true; + containsEscape = true; + } else { + /*APPJET*/ + parser.addError("msg.illegal.character.appjet", + "\\"+Character.toString((char)c)); + return Token.ERROR; + } + } else { + if (c == EOF_CHAR + || !Character.isJavaIdentifierPart((char)c)) + { + break; + } + addToString(c); + } + } + } + ungetChar(c); + + String str = getStringFromBuffer(); + /*APPJET*//*MOVED*/this.string = (String)allStrings.intern(str); + /*APPJET this move lets the names of RESERVED tokens and other + tokens be determined, and also fixes broken yield/let parsing + under pre-1.7 JS */ + if (!containsEscape) { + // OPT we shouldn't have to make a string (object!) to + // check if it's a keyword. + + // Return the corresponding token if it's a keyword + int result = stringToKeyword(str); + if (result != Token.EOF) { + if ((result == Token.LET || result == Token.YIELD) && + parser.compilerEnv.getLanguageVersion() + < Context.VERSION_1_7) + { + // LET and YIELD are tokens only in 1.7 and later + result = Token.NAME; + } + if (result != Token.RESERVED) { + return result; + } else if (!parser.compilerEnv. + isReservedKeywordAsIdentifier()) + { + return result; + } else { + // If implementation permits to use future reserved + // keywords in violation with the EcmaScript, + // treat it as name but issue warning + parser.addWarning("msg.reserved.keyword", str); + } + } + } + return Token.NAME; + } + + // is it a number? + if (isDigit(c) || (c == '.' && isDigit(peekChar()))) { + + stringBufferTop = 0; + int base = 10; + + if (c == '0') { + c = getChar(); + if (c == 'x' || c == 'X') { + base = 16; + c = getChar(); + } else if (isDigit(c)) { + base = 8; + } else { + addToString('0'); + } + } + + if (base == 16) { + while (0 <= Kit.xDigitToInt(c, 0)) { + addToString(c); + c = getChar(); + } + } else { + while ('0' <= c && c <= '9') { + /* + * We permit 08 and 09 as decimal numbers, which + * makes our behavior a superset of the ECMA + * numeric grammar. We might not always be so + * permissive, so we warn about it. + */ + if (base == 8 && c >= '8') { + parser.addWarning("msg.bad.octal.literal", + c == '8' ? "8" : "9"); + base = 10; + } + addToString(c); + c = getChar(); + } + } + + boolean isInteger = true; + + if (base == 10 && (c == '.' || c == 'e' || c == 'E')) { + isInteger = false; + if (c == '.') { + do { + addToString(c); + c = getChar(); + } while (isDigit(c)); + } + if (c == 'e' || c == 'E') { + addToString(c); + c = getChar(); + if (c == '+' || c == '-') { + addToString(c); + c = getChar(); + } + if (!isDigit(c)) { + parser.addError("msg.missing.exponent"); + return Token.ERROR; + } + do { + addToString(c); + c = getChar(); + } while (isDigit(c)); + } + } + ungetChar(c); + String numString = getStringFromBuffer(); + + double dval; + if (base == 10 && !isInteger) { + try { + // Use Java conversion to number from string... + dval = Double.valueOf(numString).doubleValue(); + } + catch (NumberFormatException ex) { + parser.addError("msg.caught.nfe"); + return Token.ERROR; + } + } else { + dval = ScriptRuntime.stringToNumber(numString, 0, base); + } + + this.number = dval; + return Token.NUMBER; + } + + // is it a string? + if (c == '"' || c == '\'') { + // We attempt to accumulate a string the fast way, by + // building it directly out of the reader. But if there + // are any escaped characters in the string, we revert to + // building it out of a StringBuffer. + + /*APPJET*/ + if (c == '"' && tryReadTripleQuotedString()) { + return Token.STRING; + } + + int quoteChar = c; + stringBufferTop = 0; + + c = getChar(); + strLoop: while (c != quoteChar) { + if (c == '\n' || c == EOF_CHAR) { + ungetChar(c); + /*APPJET*/ + parser.addError("msg.unterminated.string.lit", + Character.toString((char)quoteChar)); + return Token.ERROR; + } + + if (c == '\\') { + // We've hit an escaped character + int escapeVal; + + c = getChar(); + switch (c) { + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + + // \v a late addition to the ECMA spec, + // it is not in Java, so use 0xb + case 'v': c = 0xb; break; + + case 'u': + // Get 4 hex digits; if the u escape is not + // followed by 4 hex digits, use 'u' + the + // literal character sequence that follows. + int escapeStart = stringBufferTop; + addToString('u'); + escapeVal = 0; + for (int i = 0; i != 4; ++i) { + c = getChar(); + escapeVal = Kit.xDigitToInt(c, escapeVal); + if (escapeVal < 0) { + continue strLoop; + } + addToString(c); + } + // prepare for replace of stored 'u' sequence + // by escape value + stringBufferTop = escapeStart; + c = escapeVal; + break; + case 'x': + // Get 2 hex digits, defaulting to 'x'+literal + // sequence, as above. + c = getChar(); + escapeVal = Kit.xDigitToInt(c, 0); + if (escapeVal < 0) { + addToString('x'); + continue strLoop; + } else { + int c1 = c; + c = getChar(); + escapeVal = Kit.xDigitToInt(c, escapeVal); + if (escapeVal < 0) { + addToString('x'); + addToString(c1); + continue strLoop; + } else { + // got 2 hex digits + c = escapeVal; + } + } + break; + + case '\n': + // Remove line terminator after escape to follow + // SpiderMonkey and C/C++ + c = getChar(); + continue strLoop; + + default: + if ('0' <= c && c < '8') { + int val = c - '0'; + c = getChar(); + if ('0' <= c && c < '8') { + val = 8 * val + c - '0'; + c = getChar(); + if ('0' <= c && c < '8' && val <= 037) { + // c is 3rd char of octal sequence only + // if the resulting val <= 0377 + val = 8 * val + c - '0'; + c = getChar(); + } + } + ungetChar(c); + c = val; + } + } + } + addToString(c); + c = getChar(); + } + + String str = getStringFromBuffer(); + this.string = (String)allStrings.intern(str); + return Token.STRING; + } + + switch (c) { + case ';': return Token.SEMI; + case '[': return Token.LB; + case ']': return Token.RB; + case '{': return Token.LC; + case '}': return Token.RC; + case '(': return Token.LP; + case ')': return Token.RP; + case ',': return Token.COMMA; + case '?': return Token.HOOK; + case ':': + if (matchChar(':')) { + return Token.COLONCOLON; + } else { + return Token.COLON; + } + case '.': + if (matchChar('.')) { + return Token.DOTDOT; + } else if (matchChar('(')) { + return Token.DOTQUERY; + } else { + return Token.DOT; + } + + case '|': + if (matchChar('|')) { + return Token.OR; + } else if (matchChar('=')) { + return Token.ASSIGN_BITOR; + } else { + return Token.BITOR; + } + + case '^': + if (matchChar('=')) { + return Token.ASSIGN_BITXOR; + } else { + return Token.BITXOR; + } + + case '&': + if (matchChar('&')) { + return Token.AND; + } else if (matchChar('=')) { + return Token.ASSIGN_BITAND; + } else { + return Token.BITAND; + } + + case '=': + if (matchChar('=')) { + if (matchChar('=')) + return Token.SHEQ; + else + return Token.EQ; + } else { + return Token.ASSIGN; + } + + case '!': + if (matchChar('=')) { + if (matchChar('=')) + return Token.SHNE; + else + return Token.NE; + } else { + return Token.NOT; + } + + case '<': + /* NB:treat HTML begin-comment as comment-till-eol */ + if (matchChar('!')) { + if (matchChar('-')) { + if (matchChar('-')) { + skipLine(); + continue retry; + } + ungetChar('-'); + } + ungetChar('!'); + } + if (matchChar('<')) { + if (matchChar('=')) { + return Token.ASSIGN_LSH; + } else { + return Token.LSH; + } + } else { + if (matchChar('=')) { + return Token.LE; + } else { + return Token.LT; + } + } + + case '>': + if (matchChar('>')) { + if (matchChar('>')) { + if (matchChar('=')) { + return Token.ASSIGN_URSH; + } else { + return Token.URSH; + } + } else { + if (matchChar('=')) { + return Token.ASSIGN_RSH; + } else { + return Token.RSH; + } + } + } else { + if (matchChar('=')) { + return Token.GE; + } else { + return Token.GT; + } + } + + case '*': + if (matchChar('=')) { + return Token.ASSIGN_MUL; + } else { + return Token.MUL; + } + + case '/': + // is it a // comment? + if (matchChar('/')) { + skipLine(); + continue retry; + } + if (matchChar('*')) { + boolean lookForSlash = false; + for (;;) { + c = getChar(); + if (c == EOF_CHAR) { + parser.addError("msg.unterminated.comment"); + return Token.ERROR; + } else if (c == '*') { + lookForSlash = true; + } else if (c == '/') { + if (lookForSlash) { + continue retry; + } + } else { + lookForSlash = false; + } + } + } + + if (matchChar('=')) { + return Token.ASSIGN_DIV; + } else { + return Token.DIV; + } + + case '%': + if (matchChar('=')) { + return Token.ASSIGN_MOD; + } else { + return Token.MOD; + } + + case '~': + return Token.BITNOT; + + case '+': + if (matchChar('=')) { + return Token.ASSIGN_ADD; + } else if (matchChar('+')) { + return Token.INC; + } else { + return Token.ADD; + } + + case '-': + if (matchChar('=')) { + c = Token.ASSIGN_SUB; + } else if (matchChar('-')) { + if (!dirtyLine) { + // treat HTML end-comment after possible whitespace + // after line start as comment-utill-eol + if (matchChar('>')) { + skipLine(); + continue retry; + } + } + c = Token.DEC; + } else { + c = Token.SUB; + } + dirtyLine = true; + return c; + + default: + /*APPJET*/ + parser.addError("msg.illegal.character.appjet", + Character.toString((char)c)); + return Token.ERROR; + } + } + } + + private static boolean isAlpha(int c) + { + // Use 'Z' < 'a' + if (c <= 'Z') { + return 'A' <= c; + } else { + return 'a' <= c && c <= 'z'; + } + } + + static boolean isDigit(int c) + { + return '0' <= c && c <= '9'; + } + + /* As defined in ECMA. jsscan.c uses C isspace() (which allows + * \v, I think.) note that code in getChar() implicitly accepts + * '\r' == \u000D as well. + */ + static boolean isJSSpace(int c) + { + if (c <= 127) { + return c == 0x20 || c == 0x9 || c == 0xC || c == 0xB; + } else { + return c == 0xA0 + || Character.getType((char)c) == Character.SPACE_SEPARATOR; + } + } + + private static boolean isJSFormatChar(int c) + { + return c > 127 && Character.getType((char)c) == Character.FORMAT; + } + + /** + * Parser calls the method when it gets / or /= in literal context. + */ + void readRegExp(int startToken) + throws IOException + { + stringBufferTop = 0; + if (startToken == Token.ASSIGN_DIV) { + // Miss-scanned /= + addToString('='); + } else { + if (startToken != Token.DIV) Kit.codeBug(); + } + + int c; + while ((c = getChar()) != '/') { + if (c == '\n' || c == EOF_CHAR) { + ungetChar(c); + throw parser.reportError("msg.unterminated.re.lit"); + } + if (c == '\\') { + addToString(c); + c = getChar(); + } + + addToString(c); + } + int reEnd = stringBufferTop; + + while (true) { + if (matchChar('g')) + addToString('g'); + else if (matchChar('i')) + addToString('i'); + else if (matchChar('m')) + addToString('m'); + else + break; + } + + if (isAlpha(peekChar())) { + throw parser.reportError("msg.invalid.re.flag"); + } + + this.string = new String(stringBuffer, 0, reEnd); + this.regExpFlags = new String(stringBuffer, reEnd, + stringBufferTop - reEnd); + } + + boolean isXMLAttribute() + { + return xmlIsAttribute; + } + + int getFirstXMLToken() throws IOException + { + xmlOpenTagsCount = 0; + xmlIsAttribute = false; + xmlIsTagContent = false; + ungetChar('<'); + return getNextXMLToken(); + } + + int getNextXMLToken() throws IOException + { + stringBufferTop = 0; // remember the XML + + for (int c = getChar(); c != EOF_CHAR; c = getChar()) { + if (xmlIsTagContent) { + switch (c) { + case '>': + addToString(c); + xmlIsTagContent = false; + xmlIsAttribute = false; + break; + case '/': + addToString(c); + if (peekChar() == '>') { + c = getChar(); + addToString(c); + xmlIsTagContent = false; + xmlOpenTagsCount--; + } + break; + case '{': + ungetChar(c); + this.string = getStringFromBuffer(); + return Token.XML; + case '\'': + case '"': + addToString(c); + if (!readQuotedString(c)) return Token.ERROR; + break; + case '=': + addToString(c); + xmlIsAttribute = true; + break; + case ' ': + case '\t': + case '\r': + case '\n': + addToString(c); + break; + default: + addToString(c); + xmlIsAttribute = false; + break; + } + + if (!xmlIsTagContent && xmlOpenTagsCount == 0) { + this.string = getStringFromBuffer(); + return Token.XMLEND; + } + } else { + switch (c) { + case '<': + addToString(c); + c = peekChar(); + switch (c) { + case '!': + c = getChar(); // Skip ! + addToString(c); + c = peekChar(); + switch (c) { + case '-': + c = getChar(); // Skip - + addToString(c); + c = getChar(); + if (c == '-') { + addToString(c); + if(!readXmlComment()) return Token.ERROR; + } else { + // throw away the string in progress + stringBufferTop = 0; + this.string = null; + parser.addError("msg.XML.bad.form"); + return Token.ERROR; + } + break; + case '[': + c = getChar(); // Skip [ + addToString(c); + if (getChar() == 'C' && + getChar() == 'D' && + getChar() == 'A' && + getChar() == 'T' && + getChar() == 'A' && + getChar() == '[') + { + addToString('C'); + addToString('D'); + addToString('A'); + addToString('T'); + addToString('A'); + addToString('['); + if (!readCDATA()) return Token.ERROR; + + } else { + // throw away the string in progress + stringBufferTop = 0; + this.string = null; + parser.addError("msg.XML.bad.form"); + return Token.ERROR; + } + break; + default: + if(!readEntity()) return Token.ERROR; + break; + } + break; + case '?': + c = getChar(); // Skip ? + addToString(c); + if (!readPI()) return Token.ERROR; + break; + case '/': + // End tag + c = getChar(); // Skip / + addToString(c); + if (xmlOpenTagsCount == 0) { + // throw away the string in progress + stringBufferTop = 0; + this.string = null; + parser.addError("msg.XML.bad.form"); + return Token.ERROR; + } + xmlIsTagContent = true; + xmlOpenTagsCount--; + break; + default: + // Start tag + xmlIsTagContent = true; + xmlOpenTagsCount++; + break; + } + break; + case '{': + ungetChar(c); + this.string = getStringFromBuffer(); + return Token.XML; + default: + addToString(c); + break; + } + } + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return Token.ERROR; + } + + /** + * + */ + private boolean readQuotedString(int quote) throws IOException + { + for (int c = getChar(); c != EOF_CHAR; c = getChar()) { + addToString(c); + if (c == quote) return true; + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return false; + } + + /** + * + */ + private boolean readXmlComment() throws IOException + { + for (int c = getChar(); c != EOF_CHAR;) { + addToString(c); + if (c == '-' && peekChar() == '-') { + c = getChar(); + addToString(c); + if (peekChar() == '>') { + c = getChar(); // Skip > + addToString(c); + return true; + } else { + continue; + } + } + c = getChar(); + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return false; + } + + /** + * + */ + private boolean readCDATA() throws IOException + { + for (int c = getChar(); c != EOF_CHAR;) { + addToString(c); + if (c == ']' && peekChar() == ']') { + c = getChar(); + addToString(c); + if (peekChar() == '>') { + c = getChar(); // Skip > + addToString(c); + return true; + } else { + continue; + } + } + c = getChar(); + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return false; + } + + /** + * + */ + private boolean readEntity() throws IOException + { + int declTags = 1; + for (int c = getChar(); c != EOF_CHAR; c = getChar()) { + addToString(c); + switch (c) { + case '<': + declTags++; + break; + case '>': + declTags--; + if (declTags == 0) return true; + break; + } + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return false; + } + + /** + * + */ + private boolean readPI() throws IOException + { + for (int c = getChar(); c != EOF_CHAR; c = getChar()) { + addToString(c); + if (c == '?' && peekChar() == '>') { + c = getChar(); // Skip > + addToString(c); + return true; + } + } + + stringBufferTop = 0; // throw away the string in progress + this.string = null; + parser.addError("msg.XML.bad.form"); + return false; + } + + private String getStringFromBuffer() + { + return new String(stringBuffer, 0, stringBufferTop); + } + + private void addToString(int c) + { + int N = stringBufferTop; + if (N == stringBuffer.length) { + char[] tmp = new char[stringBuffer.length * 2]; + System.arraycopy(stringBuffer, 0, tmp, 0, N); + stringBuffer = tmp; + } + stringBuffer[N] = (char)c; + stringBufferTop = N + 1; + } + + private void ungetChar(int c) + { + // can not unread past across line boundary + if (ungetCursor != 0 && ungetBuffer[ungetCursor - 1] == '\n') + Kit.codeBug(); + ungetBuffer[ungetCursor++] = c; + } + + private boolean matchChar(int test) throws IOException + { + int c = getChar(); + if (c == test) { + return true; + } else { + ungetChar(c); + return false; + } + } + + private int peekChar() throws IOException + { + int c = getChar(); + ungetChar(c); + return c; + } + + private int getChar() throws IOException + { + if (ungetCursor != 0) { + return ungetBuffer[--ungetCursor]; + } + + for(;;) { + int c; + if (sourceString != null) { + if (sourceCursor == sourceEnd) { + hitEOF = true; + return EOF_CHAR; + } + c = sourceString.charAt(sourceCursor++); + } else { + if (sourceCursor == sourceEnd) { + if (!fillSourceBuffer()) { + hitEOF = true; + return EOF_CHAR; + } + } + c = sourceBuffer[sourceCursor++]; + } + + if (lineEndChar >= 0) { + if (lineEndChar == '\r' && c == '\n') { + lineEndChar = '\n'; + continue; + } + lineEndChar = -1; + lineStart = sourceCursor - 1; + lineno++; + } + + if (c <= 127) { + if (c == '\n' || c == '\r') { + lineEndChar = c; + c = '\n'; + } + } else { + if (isJSFormatChar(c)) { + continue; + } + if (ScriptRuntime.isJSLineTerminator(c)) { + lineEndChar = c; + c = '\n'; + } + } + return c; + } + } + + private void skipLine() throws IOException + { + // skip to end of line + int c; + while ((c = getChar()) != EOF_CHAR && c != '\n') { } + ungetChar(c); + } + + final int getOffset() + { + int n = sourceCursor - lineStart; + if (lineEndChar >= 0) { --n; } + return n; + } + + final String getLine() + { + if (sourceString != null) { + // String case + int lineEnd = sourceCursor; + if (lineEndChar >= 0) { + --lineEnd; + } else { + for (; lineEnd != sourceEnd; ++lineEnd) { + int c = sourceString.charAt(lineEnd); + if (ScriptRuntime.isJSLineTerminator(c)) { + break; + } + } + } + return sourceString.substring(lineStart, lineEnd); + } else { + // Reader case + int lineLength = sourceCursor - lineStart; + if (lineEndChar >= 0) { + --lineLength; + } else { + // Read until the end of line + for (;; ++lineLength) { + int i = lineStart + lineLength; + if (i == sourceEnd) { + try { + if (!fillSourceBuffer()) { break; } + } catch (IOException ioe) { + // ignore it, we're already displaying an error... + break; + } + // i recalculuation as fillSourceBuffer can move saved + // line buffer and change lineStart + i = lineStart + lineLength; + } + int c = sourceBuffer[i]; + if (ScriptRuntime.isJSLineTerminator(c)) { + break; + } + } + } + return new String(sourceBuffer, lineStart, lineLength); + } + } + + private boolean fillSourceBuffer() throws IOException + { + if (sourceString != null) Kit.codeBug(); + if (sourceEnd == sourceBuffer.length) { + if (lineStart != 0) { + System.arraycopy(sourceBuffer, lineStart, sourceBuffer, 0, + sourceEnd - lineStart); + sourceEnd -= lineStart; + sourceCursor -= lineStart; + lineStart = 0; + } else { + char[] tmp = new char[sourceBuffer.length * 2]; + System.arraycopy(sourceBuffer, 0, tmp, 0, sourceEnd); + sourceBuffer = tmp; + } + } + int n = sourceReader.read(sourceBuffer, sourceEnd, + sourceBuffer.length - sourceEnd); + if (n < 0) { + return false; + } + sourceEnd += n; + return true; + } + + // stuff other than whitespace since start of line + private boolean dirtyLine; + + String regExpFlags; + + // Set this to an inital non-null value so that the Parser has + // something to retrieve even if an error has occured and no + // string is found. Fosters one class of error, but saves lots of + // code. + private String string = ""; + private double number; + + private char[] stringBuffer = new char[128]; + private int stringBufferTop; + private ObjToIntMap allStrings = new ObjToIntMap(50); + + // Room to backtrace from to < on failed match of the last - in <!-- + private final int[] ungetBuffer = new int[3]; + private int ungetCursor; + + private boolean hitEOF = false; + + private int lineStart = 0; + private int lineno; + private int lineEndChar = -1; + + private String sourceString; + private Reader sourceReader; + private char[] sourceBuffer; + private int sourceEnd; + private int sourceCursor; + + // for xml tokenizer + private boolean xmlIsAttribute; + private boolean xmlIsTagContent; + private int xmlOpenTagsCount; + + private Parser parser; + + /*APPJET*//* added method */ + private boolean tryReadTripleQuotedString() throws IOException { + // have to be kind of clever, because we don't want to + // add to the overhead of tokenizing, but there are constraints + // like "once you've gotten a newline and put it back (ungotten it), + // you can't put anything else back" + + // have seen one quote + int c = getChar(); + if (c != '"') { ungetChar(c); return false; } + // have seen two quotes + c = getChar(); + if (c != '"') { + ungetChar(c); + this.string = (String)allStrings.intern(""); + return true; + } + // have seen three quotes + + final boolean newPolicy = false; + + stringBufferTop = 0; + + boolean afterBackslash = false; + boolean done = false; + while (! done) { + c = getChar(); + if (c == EOF_CHAR) { + ungetChar(c); + throw parser.reportError("msg.unterminated.mstring.appjet"); + } + if (c == '"') { + int quoteCount = 0; + int quoteLimit = 3; + if (newPolicy && ! afterBackslash) + quoteLimit = 5; + while (c == '"' && quoteCount < quoteLimit) { + quoteCount++; + c = getChar(); + } + ungetChar(c); + + if (afterBackslash) { + if (quoteCount < 3) addToString('\\'); + } + else if (quoteCount >= 3) { + quoteCount -= 3; + done = true; + } + + while (quoteCount > 0) { + addToString('"'); quoteCount--; + } + afterBackslash = false; + } + else { + if (afterBackslash) { + addToString('\\'); + afterBackslash = false; + } + if (c == '\\') { + afterBackslash = true; + } + else { + addToString(c); + } + } + } + + String str = getStringFromBuffer(); + this.string = (String)allStrings.intern(str); + + return true; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UintMap.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UintMap.java new file mode 100644 index 0000000..0027819 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UintMap.java @@ -0,0 +1,659 @@ +/* -*- 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): + * Igor Bukanov + * + * 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.io.Serializable; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Map to associate non-negative integers to objects or integers. + * The map does not synchronize any of its operation, so either use + * it from a single thread or do own synchronization or perform all mutation + * operations on one thread before passing the map to others. + * + * @author Igor Bukanov + * + */ + +public class UintMap implements Serializable +{ + static final long serialVersionUID = 4242698212885848444L; + +// Map implementation via hashtable, +// follows "The Art of Computer Programming" by Donald E. Knuth + + public UintMap() { + this(4); + } + + public UintMap(int initialCapacity) { + if (initialCapacity < 0) Kit.codeBug(); + // Table grow when number of stored keys >= 3/4 of max capacity + int minimalCapacity = initialCapacity * 4 / 3; + int i; + for (i = 2; (1 << i) < minimalCapacity; ++i) { } + power = i; + if (check && power < 2) Kit.codeBug(); + } + + public boolean isEmpty() { + return keyCount == 0; + } + + public int size() { + return keyCount; + } + + public boolean has(int key) { + if (key < 0) Kit.codeBug(); + return 0 <= findIndex(key); + } + + /** + * Get object value assigned with key. + * @return key object value or null if key is absent + */ + public Object getObject(int key) { + if (key < 0) Kit.codeBug(); + if (values != null) { + int index = findIndex(key); + if (0 <= index) { + return values[index]; + } + } + return null; + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key is absent + */ + public int getInt(int key, int defaultValue) { + if (key < 0) Kit.codeBug(); + int index = findIndex(key); + if (0 <= index) { + if (ivaluesShift != 0) { + return keys[ivaluesShift + index]; + } + return 0; + } + return defaultValue; + } + + /** + * Get integer value assigned with key. + * @return key integer value or defaultValue if key does not exist or does + * not have int value + * @throws RuntimeException if key does not exist + */ + public int getExistingInt(int key) { + if (key < 0) Kit.codeBug(); + int index = findIndex(key); + if (0 <= index) { + if (ivaluesShift != 0) { + return keys[ivaluesShift + index]; + } + return 0; + } + // Key must exist + Kit.codeBug(); + return 0; + } + + /** + * Set object value of the key. + * If key does not exist, also set its int value to 0. + */ + public void put(int key, Object value) { + if (key < 0) Kit.codeBug(); + int index = ensureIndex(key, false); + if (values == null) { + values = new Object[1 << power]; + } + values[index] = value; + } + + /** + * Set int value of the key. + * If key does not exist, also set its object value to null. + */ + public void put(int key, int value) { + if (key < 0) Kit.codeBug(); + int index = ensureIndex(key, true); + if (ivaluesShift == 0) { + int N = 1 << power; + // keys.length can be N * 2 after clear which set ivaluesShift to 0 + if (keys.length != N * 2) { + int[] tmp = new int[N * 2]; + System.arraycopy(keys, 0, tmp, 0, N); + keys = tmp; + } + ivaluesShift = N; + } + keys[ivaluesShift + index] = value; + } + + public void remove(int key) { + if (key < 0) Kit.codeBug(); + int index = findIndex(key); + if (0 <= index) { + keys[index] = DELETED; + --keyCount; + // Allow to GC value and make sure that new key with the deleted + // slot shall get proper default values + if (values != null) { values[index] = null; } + if (ivaluesShift != 0) { keys[ivaluesShift + index] = 0; } + } + } + + public void clear() { + int N = 1 << power; + if (keys != null) { + for (int i = 0; i != N; ++i) { + keys[i] = EMPTY; + } + if (values != null) { + for (int i = 0; i != N; ++i) { + values[i] = null; + } + } + } + ivaluesShift = 0; + keyCount = 0; + occupiedCount = 0; + } + + /** Return array of present keys */ + public int[] getKeys() { + int[] keys = this.keys; + int n = keyCount; + int[] result = new int[n]; + for (int i = 0; n != 0; ++i) { + int entry = keys[i]; + if (entry != EMPTY && entry != DELETED) { + result[--n] = entry; + } + } + return result; + } + + private static int tableLookupStep(int fraction, int mask, int power) { + int shift = 32 - 2 * power; + if (shift >= 0) { + return ((fraction >>> shift) & mask) | 1; + } + else { + return (fraction & (mask >>> -shift)) | 1; + } + } + + private int findIndex(int key) { + int[] keys = this.keys; + if (keys != null) { + int fraction = key * A; + int index = fraction >>> (32 - power); + int entry = keys[index]; + if (entry == key) { return index; } + if (entry != EMPTY) { + // Search in table after first failed attempt + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + do { + if (check) { + if (n >= occupiedCount) Kit.codeBug(); + ++n; + } + index = (index + step) & mask; + entry = keys[index]; + if (entry == key) { return index; } + } while (entry != EMPTY); + } + } + return -1; + } + +// Insert key that is not present to table without deleted entries +// and enough free space + private int insertNewKey(int key) { + if (check && occupiedCount != keyCount) Kit.codeBug(); + if (check && keyCount == 1 << power) Kit.codeBug(); + int[] keys = this.keys; + int fraction = key * A; + int index = fraction >>> (32 - power); + if (keys[index] != EMPTY) { + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int firstIndex = index; + do { + if (check && keys[index] == DELETED) Kit.codeBug(); + index = (index + step) & mask; + if (check && firstIndex == index) Kit.codeBug(); + } while (keys[index] != EMPTY); + } + keys[index] = key; + ++occupiedCount; + ++keyCount; + return index; + } + + private void rehashTable(boolean ensureIntSpace) { + if (keys != null) { + // Check if removing deleted entries would free enough space + if (keyCount * 2 >= occupiedCount) { + // Need to grow: less then half of deleted entries + ++power; + } + } + int N = 1 << power; + int[] old = keys; + int oldShift = ivaluesShift; + if (oldShift == 0 && !ensureIntSpace) { + keys = new int[N]; + } + else { + ivaluesShift = N; keys = new int[N * 2]; + } + for (int i = 0; i != N; ++i) { keys[i] = EMPTY; } + + Object[] oldValues = values; + if (oldValues != null) { values = new Object[N]; } + + int oldCount = keyCount; + occupiedCount = 0; + if (oldCount != 0) { + keyCount = 0; + for (int i = 0, remaining = oldCount; remaining != 0; ++i) { + int key = old[i]; + if (key != EMPTY && key != DELETED) { + int index = insertNewKey(key); + if (oldValues != null) { + values[index] = oldValues[i]; + } + if (oldShift != 0) { + keys[ivaluesShift + index] = old[oldShift + i]; + } + --remaining; + } + } + } + } + +// Ensure key index creating one if necessary + private int ensureIndex(int key, boolean intType) { + int index = -1; + int firstDeleted = -1; + int[] keys = this.keys; + if (keys != null) { + int fraction = key * A; + index = fraction >>> (32 - power); + int entry = keys[index]; + if (entry == key) { return index; } + if (entry != EMPTY) { + if (entry == DELETED) { firstDeleted = index; } + // Search in table after first failed attempt + int mask = (1 << power) - 1; + int step = tableLookupStep(fraction, mask, power); + int n = 0; + do { + if (check) { + if (n >= occupiedCount) Kit.codeBug(); + ++n; + } + index = (index + step) & mask; + entry = keys[index]; + if (entry == key) { return index; } + if (entry == DELETED && firstDeleted < 0) { + firstDeleted = index; + } + } while (entry != EMPTY); + } + } + // Inserting of new key + if (check && keys != null && keys[index] != EMPTY) + Kit.codeBug(); + if (firstDeleted >= 0) { + index = firstDeleted; + } + else { + // Need to consume empty entry: check occupation level + if (keys == null || occupiedCount * 4 >= (1 << power) * 3) { + // Too litle unused entries: rehash + rehashTable(intType); + keys = this.keys; + return insertNewKey(key); + } + ++occupiedCount; + } + keys[index] = key; + ++keyCount; + return index; + } + + private void writeObject(ObjectOutputStream out) + throws IOException + { + out.defaultWriteObject(); + + int count = keyCount; + if (count != 0) { + boolean hasIntValues = (ivaluesShift != 0); + boolean hasObjectValues = (values != null); + out.writeBoolean(hasIntValues); + out.writeBoolean(hasObjectValues); + + for (int i = 0; count != 0; ++i) { + int key = keys[i]; + if (key != EMPTY && key != DELETED) { + --count; + out.writeInt(key); + if (hasIntValues) { + out.writeInt(keys[ivaluesShift + i]); + } + if (hasObjectValues) { + out.writeObject(values[i]); + } + } + } + } + } + + private void readObject(ObjectInputStream in) + throws IOException, ClassNotFoundException + { + in.defaultReadObject(); + + int writtenKeyCount = keyCount; + if (writtenKeyCount != 0) { + keyCount = 0; + boolean hasIntValues = in.readBoolean(); + boolean hasObjectValues = in.readBoolean(); + + int N = 1 << power; + if (hasIntValues) { + keys = new int[2 * N]; + ivaluesShift = N; + }else { + keys = new int[N]; + } + for (int i = 0; i != N; ++i) { + keys[i] = EMPTY; + } + if (hasObjectValues) { + values = new Object[N]; + } + for (int i = 0; i != writtenKeyCount; ++i) { + int key = in.readInt(); + int index = insertNewKey(key); + if (hasIntValues) { + int ivalue = in.readInt(); + keys[ivaluesShift + index] = ivalue; + } + if (hasObjectValues) { + values[index] = in.readObject(); + } + } + } + } + +// A == golden_ratio * (1 << 32) = ((sqrt(5) - 1) / 2) * (1 << 32) +// See Knuth etc. + private static final int A = 0x9e3779b9; + + private static final int EMPTY = -1; + private static final int DELETED = -2; + +// Structure of kyes and values arrays (N == 1 << power): +// keys[0 <= i < N]: key value or EMPTY or DELETED mark +// values[0 <= i < N]: value of key at keys[i] +// keys[N <= i < 2N]: int values of keys at keys[i - N] + + private transient int[] keys; + private transient Object[] values; + + private int power; + private int keyCount; + private transient int occupiedCount; // == keyCount + deleted_count + + // If ivaluesShift != 0, keys[ivaluesShift + index] contains integer + // values associated with keys + private transient int ivaluesShift; + +// If true, enables consitency checks + private static final boolean check = false; + +/* TEST START + + public static void main(String[] args) { + if (!check) { + System.err.println("Set check to true and re-run"); + throw new RuntimeException("Set check to true and re-run"); + } + + UintMap map; + map = new UintMap(); + testHash(map, 2); + map = new UintMap(); + testHash(map, 10 * 1000); + map = new UintMap(30 * 1000); + testHash(map, 10 * 100); + map.clear(); + testHash(map, 4); + map = new UintMap(0); + testHash(map, 10 * 100); + } + + private static void testHash(UintMap map, int N) { + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, i); + check(i == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, i); + check(i == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i, new Integer(i)); + check(-1 == map.getInt(i, -1)); + Integer obj = (Integer)map.getObject(i); + check(obj != null && i == obj.intValue()); + } + + check(map.size() == N); + + System.out.print("."); System.out.flush(); + int[] keys = map.getKeys(); + check(keys.length == N); + for (int i = 0; i != N; ++i) { + int key = keys[i]; + check(map.has(key)); + check(!map.isIntType(key)); + check(map.isObjectType(key)); + Integer obj = (Integer) map.getObject(key); + check(obj != null && key == obj.intValue()); + } + + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + check(-1 == map.getInt(i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i * i, i); + check(i == map.getInt(i * i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + check(i == map.getInt(i * i, -1)); + } + + System.out.print("."); System.out.flush(); + for (int i = 0; i != N; ++i) { + map.put(i * i, new Integer(i)); + check(-1 == map.getInt(i * i, -1)); + map.remove(i * i); + check(!map.has(i * i)); + map.put(i * i, i); + check(map.isIntType(i * i)); + check(null == map.getObject(i * i)); + map.remove(i * i); + check(!map.isObjectType(i * i)); + check(!map.isIntType(i * i)); + } + + int old_size = map.size(); + for (int i = 0; i != N; ++i) { + map.remove(i * i); + check(map.size() == old_size); + } + + System.out.print("."); System.out.flush(); + map.clear(); + check(map.size() == 0); + for (int i = 0; i != N; ++i) { + map.put(i * i, i); + map.put(i * i + 1, new Double(i+0.5)); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(0); + checkSameMaps(map, (UintMap)writeAndRead(map)); + map = new UintMap(1); + checkSameMaps(map, (UintMap)writeAndRead(map)); + map = new UintMap(1000); + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(N / 10); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, i); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(N / 10); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, i); + } + for (int i = 0; i != N / 2; ++i) { + map.remove(2*i+1); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.print("."); System.out.flush(); + map = new UintMap(); + for (int i = 0; i != N; ++i) { + map.put(2*i+1, new Double(i + 10)); + } + for (int i = 0; i != N / 2; ++i) { + map.remove(2*i+1); + } + checkSameMaps(map, (UintMap)writeAndRead(map)); + + System.out.println(); System.out.flush(); + + } + + private static void checkSameMaps(UintMap map1, UintMap map2) { + check(map1.size() == map2.size()); + int[] keys = map1.getKeys(); + check(keys.length == map1.size()); + for (int i = 0; i != keys.length; ++i) { + int key = keys[i]; + check(map2.has(key)); + check(map1.isObjectType(key) == map2.isObjectType(key)); + check(map1.isIntType(key) == map2.isIntType(key)); + Object o1 = map1.getObject(key); + Object o2 = map2.getObject(key); + if (map1.isObjectType(key)) { + check(o1.equals(o2)); + }else { + check(map1.getObject(key) == null); + check(map2.getObject(key) == null); + } + if (map1.isIntType(key)) { + check(map1.getExistingInt(key) == map2.getExistingInt(key)); + }else { + check(map1.getInt(key, -10) == -10); + check(map1.getInt(key, -11) == -11); + check(map2.getInt(key, -10) == -10); + check(map2.getInt(key, -11) == -11); + } + } + } + + private static void check(boolean condition) { + if (!condition) Kit.codeBug(); + } + + private static Object writeAndRead(Object obj) { + try { + java.io.ByteArrayOutputStream + bos = new java.io.ByteArrayOutputStream(); + java.io.ObjectOutputStream + out = new java.io.ObjectOutputStream(bos); + out.writeObject(obj); + out.close(); + byte[] data = bos.toByteArray(); + java.io.ByteArrayInputStream + bis = new java.io.ByteArrayInputStream(data); + java.io.ObjectInputStream + in = new java.io.ObjectInputStream(bis); + Object result = in.readObject(); + in.close(); + return result; + }catch (Exception ex) { + ex.printStackTrace(); + throw new RuntimeException("Unexpected"); + } + } + +// TEST END */ +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Undefined.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Undefined.java new file mode 100644 index 0000000..472f26c --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Undefined.java @@ -0,0 +1,60 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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.io.Serializable; + +/** + * This class implements the Undefined value in JavaScript. + */ +public class Undefined implements Serializable +{ + static final long serialVersionUID = 9195680630202616767L; + + public static final Object instance = new Undefined(); + + private Undefined() + { + } + + public Object readResolve() + { + return instance; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UniqueTag.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UniqueTag.java new file mode 100644 index 0000000..33f96eb --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/UniqueTag.java @@ -0,0 +1,120 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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.io.Serializable; + +/** + * Class instances represent serializable tags to mark special Object values. + * <p> + * Compatibility note: under jdk 1.1 use + * org.mozilla.javascript.serialize.ScriptableInputStream to read serialized + * instances of UniqueTag as under this JDK version the default + * ObjectInputStream would not restore them correctly as it lacks support + * for readResolve method + */ +public final class UniqueTag implements Serializable +{ + static final long serialVersionUID = -4320556826714577259L; + + private static final int ID_NOT_FOUND = 1; + private static final int ID_NULL_VALUE = 2; + private static final int ID_DOUBLE_MARK = 3; + + /** + * Tag to mark non-existing values. + */ + public static final UniqueTag + NOT_FOUND = new UniqueTag(ID_NOT_FOUND); + + /** + * Tag to distinguish between uninitialized and null values. + */ + public static final UniqueTag + NULL_VALUE = new UniqueTag(ID_NULL_VALUE); + + /** + * Tag to indicate that a object represents "double" with the real value + * stored somewhere else. + */ + public static final UniqueTag + DOUBLE_MARK = new UniqueTag(ID_DOUBLE_MARK); + + private final int tagId; + + private UniqueTag(int tagId) + { + this.tagId = tagId; + } + + public Object readResolve() + { + switch (tagId) { + case ID_NOT_FOUND: + return NOT_FOUND; + case ID_NULL_VALUE: + return NULL_VALUE; + case ID_DOUBLE_MARK: + return DOUBLE_MARK; + } + throw new IllegalStateException(String.valueOf(tagId)); + } + +// Overridden for better debug printouts + public String toString() + { + String name; + switch (tagId) { + case ID_NOT_FOUND: + name = "NOT_FOUND"; + break; + case ID_NULL_VALUE: + name = "NULL_VALUE"; + break; + case ID_DOUBLE_MARK: + name = "DOUBLE_MARK"; + break; + default: + throw Kit.codeBug(); + } + return super.toString()+": "+name; + } + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/VMBridge.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/VMBridge.java new file mode 100644 index 0000000..5fba4a5 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/VMBridge.java @@ -0,0 +1,183 @@ +/* -*- 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): + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +import java.lang.reflect.Method; +import java.lang.reflect.Member; +import java.util.Iterator; + +public abstract class VMBridge +{ + + static final VMBridge instance = makeInstance(); + + private static VMBridge makeInstance() + { + String[] classNames = { + "org.mozilla.javascript.VMBridge_custom", + "org.mozilla.javascript.jdk15.VMBridge_jdk15", + "org.mozilla.javascript.jdk13.VMBridge_jdk13", + "org.mozilla.javascript.jdk11.VMBridge_jdk11", + }; + for (int i = 0; i != classNames.length; ++i) { + String className = classNames[i]; + Class cl = Kit.classOrNull(className); + if (cl != null) { + VMBridge bridge = (VMBridge)Kit.newInstanceOrNull(cl); + if (bridge != null) { + return bridge; + } + } + } + throw new IllegalStateException("Failed to create VMBridge instance"); + } + + /** + * Return a helper object to optimize {@link Context} access. + * <p> + * The runtime will pass the resulting helper object to the subsequent + * calls to {@link #getContext(Object contextHelper)} and + * {@link #setContext(Object contextHelper, Context cx)} methods. + * In this way the implementation can use the helper to cache + * information about current thread to make {@link Context} access faster. + */ + protected abstract Object getThreadContextHelper(); + + /** + * Get {@link Context} instance associated with the current thread + * or null if none. + * + * @param contextHelper The result of {@link #getThreadContextHelper()} + * called from the current thread. + */ + protected abstract Context getContext(Object contextHelper); + + /** + * Associate {@link Context} instance with the current thread or remove + * the current association if <tt>cx</tt> is null. + * + * @param contextHelper The result of {@link #getThreadContextHelper()} + * called from the current thread. + */ + protected abstract void setContext(Object contextHelper, Context cx); + + /** + * Return the ClassLoader instance associated with the current thread. + */ + protected abstract ClassLoader getCurrentThreadClassLoader(); + + /** + * In many JVMSs, public methods in private + * classes are not accessible by default (Sun Bug #4071593). + * VMBridge instance should try to workaround that via, for example, + * calling method.setAccessible(true) when it is available. + * The implementation is responsible to catch all possible exceptions + * like SecurityException if the workaround is not available. + * + * @return true if it was possible to make method accessible + * or false otherwise. + */ + protected abstract boolean tryToMakeAccessible(Object accessibleObject); + + /** + * Create helper object to create later proxies implementing the specified + * interfaces later. Under JDK 1.3 the implementation can look like: + * <pre> + * return java.lang.reflect.Proxy.getProxyClass(..., interfaces). + * getConstructor(new Class[] { + * java.lang.reflect.InvocationHandler.class }); + * </pre> + * + * @param interfaces Array with one or more interface class objects. + */ + protected Object getInterfaceProxyHelper(ContextFactory cf, + Class[] interfaces) + { + throw Context.reportRuntimeError( + "VMBridge.getInterfaceProxyHelper is not supported"); + } + + /** + * Create proxy object for {@link InterfaceAdapter}. The proxy should call + * {@link InterfaceAdapter#invoke(ContextFactory cf, + * Object target, + * Scriptable topScope, + * Method method, + * Object[] args)} + * as implementation of interface methods associated with + * <tt>proxyHelper</tt>. + * + * @param proxyHelper The result of the previous call to + * {@link #getInterfaceProxyHelper(ContextFactory, Class[])}. + */ + protected Object newInterfaceProxy(Object proxyHelper, + ContextFactory cf, + InterfaceAdapter adapter, + Object target, + Scriptable topScope) + { + throw Context.reportRuntimeError( + "VMBridge.newInterfaceProxy is not supported"); + } + + /** + * Returns whether or not a given member (method or constructor) + * has variable arguments. + * Variable argument methods have only been supported in Java since + * JDK 1.5. + */ + protected abstract boolean isVarArgs(Member member); + + /** + * If "obj" is a java.util.Iterator or a java.lang.Iterable, return a + * wrapping as a JavaScript Iterator. Otherwise, return null. + * This method is in VMBridge since Iterable is a JDK 1.5 addition. + */ + public Iterator getJavaIterator(Context cx, Scriptable scope, Object obj) { + if (obj instanceof Wrapper) { + Object unwrapped = ((Wrapper) obj).unwrap(); + Iterator iterator = null; + if (unwrapped instanceof Iterator) + iterator = (Iterator) unwrapped; + return iterator; + } + return null; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrapFactory.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrapFactory.java new file mode 100644 index 0000000..3edc203 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrapFactory.java @@ -0,0 +1,183 @@ +/* -*- 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): + * Norris Boyd + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Embeddings that wish to provide their own custom wrappings for Java + * objects may extend this class and call + * {@link Context#setWrapFactory(WrapFactory)} + * Once an instance of this class or an extension of this class is enabled + * for a given context (by calling setWrapFactory on that context), Rhino + * will call the methods of this class whenever it needs to wrap a value + * resulting from a call to a Java method or an access to a Java field. + * + * @see org.mozilla.javascript.Context#setWrapFactory(WrapFactory) + * @since 1.5 Release 4 + */ +public class WrapFactory +{ + /** + * Wrap the object. + * <p> + * The value returned must be one of + * <UL> + * <LI>java.lang.Boolean</LI> + * <LI>java.lang.String</LI> + * <LI>java.lang.Number</LI> + * <LI>org.mozilla.javascript.Scriptable objects</LI> + * <LI>The value returned by Context.getUndefinedValue()</LI> + * <LI>null</LI> + * </UL> + * @param cx the current Context for this thread + * @param scope the scope of the executing script + * @param obj the object to be wrapped. Note it can be null. + * @param staticType type hint. If security restrictions prevent to wrap + object based on its class, staticType will be used instead. + * @return the wrapped value. + */ + public Object wrap(Context cx, Scriptable scope, + Object obj, Class staticType) + { + if (obj == null || obj == Undefined.instance + || obj instanceof Scriptable) + { + return obj; + } + if (staticType != null && staticType.isPrimitive()) { + if (staticType == Void.TYPE) + return Undefined.instance; + if (staticType == Character.TYPE) + return new Integer(((Character) obj).charValue()); + return obj; + } + if (!isJavaPrimitiveWrap()) { + if (obj instanceof String || obj instanceof Number + || obj instanceof Boolean) + { + return obj; + } else if (obj instanceof Character) { + return String.valueOf(((Character)obj).charValue()); + } + } + Class cls = obj.getClass(); + if (cls.isArray()) { + return NativeJavaArray.wrap(scope, obj); + } + return wrapAsJavaObject(cx, scope, obj, staticType); + } + + /** + * Wrap an object newly created by a constructor call. + * @param cx the current Context for this thread + * @param scope the scope of the executing script + * @param obj the object to be wrapped + * @return the wrapped value. + */ + public Scriptable wrapNewObject(Context cx, Scriptable scope, Object obj) + { + if (obj instanceof Scriptable) { + return (Scriptable)obj; + } + Class cls = obj.getClass(); + if (cls.isArray()) { + return NativeJavaArray.wrap(scope, obj); + } + return wrapAsJavaObject(cx, scope, obj, null); + } + + /** + * Wrap Java object as Scriptable instance to allow full access to its + * methods and fields from JavaScript. + * <p> + * {@link #wrap(Context, Scriptable, Object, Class)} and + * {@link #wrapNewObject(Context, Scriptable, Object)} call this method + * when they can not convert <tt>javaObject</tt> to JavaScript primitive + * value or JavaScript array. + * <p> + * Subclasses can override the method to provide custom wrappers + * for Java objects. + * @param cx the current Context for this thread + * @param scope the scope of the executing script + * @param javaObject the object to be wrapped + * @param staticType type hint. If security restrictions prevent to wrap + object based on its class, staticType will be used instead. + * @return the wrapped value which shall not be null + */ + public Scriptable wrapAsJavaObject(Context cx, Scriptable scope, + Object javaObject, Class staticType) + { + Scriptable wrap; + wrap = new NativeJavaObject(scope, javaObject, staticType); + return wrap; + } + + /** + * Return <code>false</code> if result of Java method, which is instance of + * <code>String</code>, <code>Number</code>, <code>Boolean</code> and + * <code>Character</code>, should be used directly as JavaScript primitive + * type. + * By default the method returns true to indicate that instances of + * <code>String</code>, <code>Number</code>, <code>Boolean</code> and + * <code>Character</code> should be wrapped as any other Java object and + * scripts can access any Java method available in these objects. + * Use {@link #setJavaPrimitiveWrap(boolean)} to change this. + */ + public final boolean isJavaPrimitiveWrap() + { + return javaPrimitiveWrap; + } + + /** + * @see #isJavaPrimitiveWrap() + */ + public final void setJavaPrimitiveWrap(boolean value) + { + Context cx = Context.getCurrentContext(); + if (cx != null && cx.isSealed()) { + Context.onSealedMutation(); + } + javaPrimitiveWrap = value; + } + + private boolean javaPrimitiveWrap = true; + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrappedException.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrappedException.java new file mode 100644 index 0000000..c749f74 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/WrappedException.java @@ -0,0 +1,93 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * 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; + +/** + * A wrapper for runtime exceptions. + * + * Used by the JavaScript runtime to wrap and propagate exceptions that occur + * during runtime. + * + * @author Norris Boyd + */ +public class WrappedException extends EvaluatorException +{ + static final long serialVersionUID = -1551979216966520648L; + + /** + * @see Context#throwAsScriptRuntimeEx(Throwable e) + */ + public WrappedException(Throwable exception) + { + super("Wrapped "+exception.toString()); + this.exception = exception; + Kit.initCause(this, exception); + + int[] linep = { 0 }; + String sourceName = Context.getSourcePositionFromStack(linep); + int lineNumber = linep[0]; + if (sourceName != null) { + initSourceName(sourceName); + } + if (lineNumber != 0) { + initLineNumber(lineNumber); + } + } + + /** + * Get the wrapped exception. + * + * @return the exception that was presented as a argument to the + * constructor when this object was created + */ + public Throwable getWrappedException() + { + return exception; + } + + /** + * @deprecated Use {@link #getWrappedException()} instead. + */ + public Object unwrap() + { + return getWrappedException(); + } + + private Throwable exception; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Wrapper.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Wrapper.java new file mode 100644 index 0000000..cb2d2f5 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/Wrapper.java @@ -0,0 +1,58 @@ +/* -*- 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): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript; + +/** + * Objects that can wrap other values for reflection in the JS environment + * will implement Wrapper. + * + * Wrapper defines a single method that can be called to unwrap the object. + */ + +public interface Wrapper { + + /** + * Unwrap the object by returning the wrapped value. + * + * @return a wrapped value + */ + public Object unwrap(); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/continuations/Continuation.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/continuations/Continuation.java new file mode 100644 index 0000000..c6d3966 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/continuations/Continuation.java @@ -0,0 +1,136 @@ +/* -*- 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Igor Bukanov + * + * 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.continuations; + +import org.mozilla.javascript.*; + +public final class Continuation extends IdScriptableObject implements Function +{ + static final long serialVersionUID = 1794167133757605367L; + + private static final Object FTAG = new Object(); + + private Object implementation; + + public static void init(Context cx, Scriptable scope, boolean sealed) + { + Continuation obj = new Continuation(); + obj.exportAsJSClass(MAX_PROTOTYPE_ID, scope, sealed); + } + + public Object getImplementation() + { + return implementation; + } + + public void initImplementation(Object implementation) + { + this.implementation = implementation; + } + + public String getClassName() + { + return "Continuation"; + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + throw Context.reportRuntimeError("Direct call is not supported"); + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return Interpreter.restartContinuation(this, cx, scope, args); + } + + public static boolean isContinuationConstructor(IdFunctionObject f) + { + if (f.hasTag(FTAG) && f.methodId() == Id_constructor) { + return true; + } + return false; + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_constructor: arity=0; s="constructor"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(FTAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(FTAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_constructor: + throw Context.reportRuntimeError("Direct call is not supported"); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + +// #string_id_map# + + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:16:40 EDT + L0: { id = 0; String X = null; + if (s.length()==11) { X="constructor";id=Id_constructor; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = 1, + MAX_PROTOTYPE_ID = 1; + +// #/string_id_map# +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebugFrame.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebugFrame.java new file mode 100644 index 0000000..ef15710 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebugFrame.java @@ -0,0 +1,91 @@ +/* -*- 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): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript.debug; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +/** +Interface to implement if the application is interested in receiving debug +information during execution of a particular script or function. +*/ +public interface DebugFrame { + +/** +Called when execution is ready to start bytecode interpretation for entered a particular function or script. + +@param cx current Context for this thread +@param activation the activation scope for the function or script. +@param thisObj value of the JavaScript <code>this</code> object +@param args the array of arguments +*/ + public void onEnter(Context cx, Scriptable activation, + Scriptable thisObj, Object[] args); +/** +Called when executed code reaches new line in the source. +@param cx current Context for this thread +@param lineNumber current line number in the script source +*/ + public void onLineChange(Context cx, int lineNumber); + +/** +Called when thrown exception is handled by the function or script. +@param cx current Context for this thread +@param ex exception object +*/ + public void onExceptionThrown(Context cx, Throwable ex); + +/** +Called when the function or script for this frame is about to return. +@param cx current Context for this thread +@param byThrow if true function will leave by throwing exception, otherwise it + will execute normal return +@param resultOrException function result in case of normal return or + exception object if about to throw exception +*/ + public void onExit(Context cx, boolean byThrow, Object resultOrException); + +/** +Called when the function or script executes a 'debugger' statement. +@param cx current Context for this thread +*/ + public void onDebuggerStatement(Context cx); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableObject.java new file mode 100644 index 0000000..23e7421 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableObject.java @@ -0,0 +1,61 @@ +/* -*- 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): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript.debug; + +/** + * This interface exposes debugging information from objects. + */ +public interface DebuggableObject { + + /** + * Returns an array of ids for the properties of the object. + * + * <p>All properties, even those with attribute {DontEnum}, are listed. + * This allows the debugger to display all properties of the object.<p> + * + * @return an array of java.lang.Objects with an entry for every + * listed property. Properties accessed via an integer index will + * have a corresponding + * Integer entry in the returned array. Properties accessed by + * a String will have a String entry in the returned array. + */ + public Object[] getAllIds(); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableScript.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableScript.java new file mode 100644 index 0000000..705e442 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/DebuggableScript.java @@ -0,0 +1,119 @@ +/* -*- 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): + * Norris Boyd + * Igor Bukanov + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript.debug; + +/** + * This interface exposes debugging information from executable + * code (either functions or top-level scripts). + */ +public interface DebuggableScript +{ + public boolean isTopLevel(); + + /** + * Returns true if this is a function, false if it is a script. + */ + public boolean isFunction(); + + /** + * Get name of the function described by this script. + * Return null or an empty string if this script is not a function. + */ + public String getFunctionName(); + + /** + * Get number of declared parameters in the function. + * Return 0 if this script is not a function. + * + * @see #getParamAndVarCount() + * @see #getParamOrVarName(int index) + */ + public int getParamCount(); + + /** + * Get number of declared parameters and local variables. + * Return number of declared global variables if this script is not a + * function. + * + * @see #getParamCount() + * @see #getParamOrVarName(int index) + */ + public int getParamAndVarCount(); + + /** + * Get name of a declared parameter or local variable. + * <tt>index</tt> should be less then {@link #getParamAndVarCount()}. + * If <tt>index < {@link #getParamCount()}</tt>, return + * the name of the corresponding parameter, otherwise return the name + * of variable. + * If this script is not function, return the name of the declared + * global variable. + */ + public String getParamOrVarName(int index); + + /** + * Get the name of the source (usually filename or URL) + * of the script. + */ + public String getSourceName(); + + /** + * Returns true if this script or function were runtime-generated + * from JavaScript using <tt>eval</tt> function or <tt>Function</tt> + * or <tt>Script</tt> constructors. + */ + public boolean isGeneratedScript(); + + /** + * Get array containing the line numbers that + * that can be passed to <code>DebugFrame.onLineChange()<code>. + * Note that line order in the resulting array is arbitrary + */ + public int[] getLineNumbers(); + + public int getFunctionCount(); + + public DebuggableScript getFunction(int index); + + public DebuggableScript getParent(); + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/Debugger.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/Debugger.java new file mode 100644 index 0000000..bfac153 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/debug/Debugger.java @@ -0,0 +1,69 @@ +/* -*- 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): + * Norris Boyd + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript.debug; + +import org.mozilla.javascript.Context; + +/** +Interface to implement if the application is interested in receiving debug +information. +*/ +public interface Debugger { + +/** +Called when compilation of a particular function or script into internal +bytecode is done. + +@param cx current Context for this thread +@param fnOrScript object describing the function or script +@param source the function or script source +*/ + void handleCompilationDone(Context cx, DebuggableScript fnOrScript, + String source); + +/** +Called when execution entered a particular function or script. + +@return implementation of DebugFrame which receives debug information during + the function or script execution or null otherwise +*/ + DebugFrame getFrame(Context cx, DebuggableScript fnOrScript); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java new file mode 100644 index 0000000..f5d1522 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk11/VMBridge_jdk11.java @@ -0,0 +1,84 @@ +/* -*- 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): + * + * 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.jdk11; + +import java.lang.reflect.Member; +import java.util.Hashtable; + +import org.mozilla.javascript.*; + +public class VMBridge_jdk11 extends VMBridge +{ + private Hashtable threadsWithContext = new Hashtable(); + + protected Object getThreadContextHelper() + { + return Thread.currentThread(); + } + + protected Context getContext(Object contextHelper) + { + Thread t = (Thread)contextHelper; + return (Context)threadsWithContext.get(t); + } + + protected void setContext(Object contextHelper, Context cx) + { + Thread t = (Thread)contextHelper; + if (cx == null) { + // Allow to garbage collect thread reference + threadsWithContext.remove(t); + } else { + threadsWithContext.put(t, cx); + } + } + + protected ClassLoader getCurrentThreadClassLoader() + { + return null; + } + + protected boolean tryToMakeAccessible(Object accessibleObject) + { + return false; + } + + protected boolean isVarArgs(Member member) { + return false; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java new file mode 100644 index 0000000..c33e9b4 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk13/VMBridge_jdk13.java @@ -0,0 +1,157 @@ +/* -*- 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): + * + * 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.jdk13; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Member; +import java.lang.reflect.Proxy; + +import org.mozilla.javascript.*; + +public class VMBridge_jdk13 extends VMBridge +{ + private ThreadLocal contextLocal = new ThreadLocal(); + + protected Object getThreadContextHelper() + { + // To make subsequent batch calls to getContext/setContext faster + // associate permanently one element array with contextLocal + // so getContext/setContext would need just to read/write the first + // array element. + // Note that it is necessary to use Object[], not Context[] to allow + // garbage collection of Rhino classes. For details see comments + // by Attila Szegedi in + // https://bugzilla.mozilla.org/show_bug.cgi?id=281067#c5 + + Object[] storage = (Object[])contextLocal.get(); + if (storage == null) { + storage = new Object[1]; + contextLocal.set(storage); + } + return storage; + } + + protected Context getContext(Object contextHelper) + { + Object[] storage = (Object[])contextHelper; + return (Context)storage[0]; + } + + protected void setContext(Object contextHelper, Context cx) + { + Object[] storage = (Object[])contextHelper; + storage[0] = cx; + } + + protected ClassLoader getCurrentThreadClassLoader() + { + return Thread.currentThread().getContextClassLoader(); + } + + protected boolean tryToMakeAccessible(Object accessibleObject) + { + if (!(accessibleObject instanceof AccessibleObject)) { + return false; + } + AccessibleObject accessible = (AccessibleObject)accessibleObject; + if (accessible.isAccessible()) { + return true; + } + try { + accessible.setAccessible(true); + } catch (Exception ex) { } + + return accessible.isAccessible(); + } + + protected Object getInterfaceProxyHelper(ContextFactory cf, + Class[] interfaces) + { + // XXX: How to handle interfaces array withclasses from different + // class loaders? Using cf.getApplicationClassLoader() ? + ClassLoader loader = interfaces[0].getClassLoader(); + Class cl = Proxy.getProxyClass(loader, interfaces); + Constructor c; + try { + c = cl.getConstructor(new Class[] { InvocationHandler.class }); + } catch (NoSuchMethodException ex) { + // Should not happen + throw Kit.initCause(new IllegalStateException(), ex); + } + return c; + } + + protected Object newInterfaceProxy(Object proxyHelper, + final ContextFactory cf, + final InterfaceAdapter adapter, + final Object target, + final Scriptable topScope) + { + Constructor c = (Constructor)proxyHelper; + + InvocationHandler handler = new InvocationHandler() { + public Object invoke(Object proxy, + Method method, + Object[] args) + { + return adapter.invoke(cf, target, topScope, method, args); + } + }; + Object proxy; + try { + proxy = c.newInstance(new Object[] { handler }); + } catch (InvocationTargetException ex) { + throw Context.throwAsScriptRuntimeEx(ex); + } catch (IllegalAccessException ex) { + // Shouls not happen + throw Kit.initCause(new IllegalStateException(), ex); + } catch (InstantiationException ex) { + // Shouls not happen + throw Kit.initCause(new IllegalStateException(), ex); + } + return proxy; + } + + protected boolean isVarArgs(Member member) { + return false; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java new file mode 100644 index 0000000..0ffaf9d --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/jdk15/VMBridge_jdk15.java @@ -0,0 +1,87 @@ +/* -*- 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): + * + * 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.jdk15; + +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Constructor; +import java.util.Iterator; +import org.mozilla.javascript.*; + +public class VMBridge_jdk15 extends org.mozilla.javascript.jdk13.VMBridge_jdk13 +{ + public VMBridge_jdk15() throws SecurityException, InstantiationException { + try { + // Just try and see if we can access the isVarArgs method. + // We want to fail loading if the method does not exist + // so that we can load a bridge to an older JDK instead. + Method.class.getMethod("isVarArgs", (Class[]) null); + } catch (NoSuchMethodException e) { + // Throw a fitting exception that is handled by + // org.mozilla.javascript.Kit.newInstanceOrNull: + throw new InstantiationException(e.getMessage()); + } + } + + public boolean isVarArgs(Member member) { + if (member instanceof Method) + return ((Method) member).isVarArgs(); + else if (member instanceof Constructor) + return ((Constructor) member).isVarArgs(); + else + return false; + } + + /** + * If "obj" is a java.util.Iterator or a java.lang.Iterable, return a + * wrapping as a JavaScript Iterator. Otherwise, return null. + * This method is in VMBridge since Iterable is a JDK 1.5 addition. + */ + public Iterator getJavaIterator(Context cx, Scriptable scope, Object obj) { + if (obj instanceof Wrapper) { + Object unwrapped = ((Wrapper) obj).unwrap(); + Iterator iterator = null; + if (unwrapped instanceof Iterator) + iterator = (Iterator) unwrapped; + if (unwrapped instanceof Iterable) + iterator = ((Iterable)unwrapped).iterator(); + return iterator; + } + return null; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java new file mode 100644 index 0000000..bd56714 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Block.java @@ -0,0 +1,615 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * + * 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.optimizer; + +import org.mozilla.javascript.*; + +import java.util.Hashtable; + +import java.io.PrintWriter; +import java.io.StringWriter; + +class Block +{ + + private static class FatBlock + { + + private static Block[] reduceToArray(ObjToIntMap map) + { + Block[] result = null; + if (!map.isEmpty()) { + result = new Block[map.size()]; + int i = 0; + ObjToIntMap.Iterator iter = map.newIterator(); + for (iter.start(); !iter.done(); iter.next()) { + FatBlock fb = (FatBlock)(iter.getKey()); + result[i++] = fb.realBlock; + } + } + return result; + } + + void addSuccessor(FatBlock b) { successors.put(b, 0); } + void addPredecessor(FatBlock b) { predecessors.put(b, 0); } + + Block[] getSuccessors() { return reduceToArray(successors); } + Block[] getPredecessors() { return reduceToArray(predecessors); } + + // all the Blocks that come immediately after this + private ObjToIntMap successors = new ObjToIntMap(); + // all the Blocks that come immediately before this + private ObjToIntMap predecessors = new ObjToIntMap(); + + Block realBlock; + } + + Block(int startNodeIndex, int endNodeIndex) + { + itsStartNodeIndex = startNodeIndex; + itsEndNodeIndex = endNodeIndex; + } + + static void runFlowAnalyzes(OptFunctionNode fn, Node[] statementNodes) + { + int paramCount = fn.fnode.getParamCount(); + int varCount = fn.fnode.getParamAndVarCount(); + int[] varTypes = new int[varCount]; + // If the variable is a parameter, it could have any type. + for (int i = 0; i != paramCount; ++i) { + varTypes[i] = Optimizer.AnyType; + } + // If the variable is from a "var" statement, its typeEvent will be set + // when we see the setVar node. + for (int i = paramCount; i != varCount; ++i) { + varTypes[i] = Optimizer.NoType; + } + + Block[] theBlocks = buildBlocks(statementNodes); + + if (DEBUG) { + ++debug_blockCount; + System.out.println("-------------------"+fn.fnode.getFunctionName()+" "+debug_blockCount+"--------"); + System.out.println(toString(theBlocks, statementNodes)); + } + + reachingDefDataFlow(fn, statementNodes, theBlocks, varTypes); + typeFlow(fn, statementNodes, theBlocks, varTypes); + + if (DEBUG) { + for (int i = 0; i < theBlocks.length; i++) { + System.out.println("For block " + theBlocks[i].itsBlockID); + theBlocks[i].printLiveOnEntrySet(fn); + } + System.out.println("Variable Table, size = " + varCount); + for (int i = 0; i != varCount; i++) { + System.out.println("["+i+"] type: "+varTypes[i]); + } + } + + for (int i = paramCount; i != varCount; i++) { + if (varTypes[i] == Optimizer.NumberType) { + fn.setIsNumberVar(i); + } + } + + } + + private static Block[] buildBlocks(Node[] statementNodes) + { + // a mapping from each target node to the block it begins + Hashtable theTargetBlocks = new Hashtable(); + ObjArray theBlocks = new ObjArray(); + + // there's a block that starts at index 0 + int beginNodeIndex = 0; + + for (int i = 0; i < statementNodes.length; i++) { + switch (statementNodes[i].getType()) { + case Token.TARGET : + { + if (i != beginNodeIndex) { + FatBlock fb = newFatBlock(beginNodeIndex, i - 1); + if (statementNodes[beginNodeIndex].getType() + == Token.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.add(fb); + // start the next block at this node + beginNodeIndex = i; + } + } + break; + case Token.IFNE : + case Token.IFEQ : + case Token.GOTO : + { + FatBlock fb = newFatBlock(beginNodeIndex, i); + if (statementNodes[beginNodeIndex].getType() + == Token.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.add(fb); + // start the next block at the next node + beginNodeIndex = i + 1; + } + break; + } + } + + if (beginNodeIndex != statementNodes.length) { + FatBlock fb = newFatBlock(beginNodeIndex, statementNodes.length - 1); + if (statementNodes[beginNodeIndex].getType() == Token.TARGET) + theTargetBlocks.put(statementNodes[beginNodeIndex], fb); + theBlocks.add(fb); + } + + // build successor and predecessor links + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.get(i)); + + Node blockEndNode = statementNodes[fb.realBlock.itsEndNodeIndex]; + int blockEndNodeType = blockEndNode.getType(); + + if ((blockEndNodeType != Token.GOTO) + && (i < (theBlocks.size() - 1))) { + FatBlock fallThruTarget = (FatBlock)(theBlocks.get(i + 1)); + fb.addSuccessor(fallThruTarget); + fallThruTarget.addPredecessor(fb); + } + + + if ( (blockEndNodeType == Token.IFNE) + || (blockEndNodeType == Token.IFEQ) + || (blockEndNodeType == Token.GOTO) ) { + Node target = ((Node.Jump)blockEndNode).target; + FatBlock branchTargetBlock + = (FatBlock)(theTargetBlocks.get(target)); + target.putProp(Node.TARGETBLOCK_PROP, + branchTargetBlock.realBlock); + fb.addSuccessor(branchTargetBlock); + branchTargetBlock.addPredecessor(fb); + } + } + + Block[] result = new Block[theBlocks.size()]; + + for (int i = 0; i < theBlocks.size(); i++) { + FatBlock fb = (FatBlock)(theBlocks.get(i)); + Block b = fb.realBlock; + b.itsSuccessors = fb.getSuccessors(); + b.itsPredecessors = fb.getPredecessors(); + b.itsBlockID = i; + result[i] = b; + } + + return result; + } + + private static FatBlock newFatBlock(int startNodeIndex, int endNodeIndex) + { + FatBlock fb = new FatBlock(); + fb.realBlock = new Block(startNodeIndex, endNodeIndex); + return fb; + } + + private static String toString(Block[] blockList, Node[] statementNodes) + { + if (!DEBUG) return null; + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + + pw.println(blockList.length + " Blocks"); + for (int i = 0; i < blockList.length; i++) { + Block b = blockList[i]; + pw.println("#" + b.itsBlockID); + pw.println("from " + b.itsStartNodeIndex + + " " + + statementNodes[b.itsStartNodeIndex].toString()); + pw.println("thru " + b.itsEndNodeIndex + + " " + + statementNodes[b.itsEndNodeIndex].toString()); + pw.print("Predecessors "); + if (b.itsPredecessors != null) { + for (int j = 0; j < b.itsPredecessors.length; j++) + pw.print(b.itsPredecessors[j].itsBlockID + " "); + pw.println(); + } + else + pw.println("none"); + pw.print("Successors "); + if (b.itsSuccessors != null) { + for (int j = 0; j < b.itsSuccessors.length; j++) + pw.print(b.itsSuccessors[j].itsBlockID + " "); + pw.println(); + } + else + pw.println("none"); + } + return sw.toString(); + } + + private static void reachingDefDataFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes) + { +/* + initialize the liveOnEntry and liveOnExit sets, then discover the variables + that are def'd by each function, and those that are used before being def'd + (hence liveOnEntry) +*/ + for (int i = 0; i < theBlocks.length; i++) { + theBlocks[i].initLiveOnEntrySets(fn, statementNodes); + } +/* + this visits every block starting at the last, re-adding the predecessors of + any block whose inputs change as a result of the dataflow. + REMIND, better would be to visit in CFG postorder +*/ + boolean visit[] = new boolean[theBlocks.length]; + boolean doneOnce[] = new boolean[theBlocks.length]; + int vIndex = theBlocks.length - 1; + boolean needRescan = false; + visit[vIndex] = true; + while (true) { + if (visit[vIndex] || !doneOnce[vIndex]) { + doneOnce[vIndex] = true; + visit[vIndex] = false; + if (theBlocks[vIndex].doReachedUseDataFlow()) { + Block pred[] = theBlocks[vIndex].itsPredecessors; + if (pred != null) { + for (int i = 0; i < pred.length; i++) { + int index = pred[i].itsBlockID; + visit[index] = true; + needRescan |= (index > vIndex); + } + } + } + } + if (vIndex == 0) { + if (needRescan) { + vIndex = theBlocks.length - 1; + needRescan = false; + } + else + break; + } + else + vIndex--; + } +/* + if any variable is live on entry to block 0, we have to mark it as + not jRegable - since it means that someone is trying to access the + 'undefined'-ness of that variable. +*/ + + theBlocks[0].markAnyTypeVariables(varTypes); + } + + private static void typeFlow(OptFunctionNode fn, Node[] statementNodes, Block theBlocks[], int[] varTypes) + { + boolean visit[] = new boolean[theBlocks.length]; + boolean doneOnce[] = new boolean[theBlocks.length]; + int vIndex = 0; + boolean needRescan = false; + visit[vIndex] = true; + while (true) { + if (visit[vIndex] || !doneOnce[vIndex]) { + doneOnce[vIndex] = true; + visit[vIndex] = false; + if (theBlocks[vIndex].doTypeFlow(fn, statementNodes, varTypes)) + { + Block succ[] = theBlocks[vIndex].itsSuccessors; + if (succ != null) { + for (int i = 0; i < succ.length; i++) { + int index = succ[i].itsBlockID; + visit[index] = true; + needRescan |= (index < vIndex); + } + } + } + } + if (vIndex == (theBlocks.length - 1)) { + if (needRescan) { + vIndex = 0; + needRescan = false; + } + else + break; + } + else + vIndex++; + } + } + + private static boolean assignType(int[] varTypes, int index, int type) + { + return type != (varTypes[index] |= type); + } + + private void markAnyTypeVariables(int[] varTypes) + { + for (int i = 0; i != varTypes.length; i++) { + if (itsLiveOnEntrySet.test(i)) { + assignType(varTypes, i, Optimizer.AnyType); + } + } + + } + + /* + We're tracking uses and defs - in order to + build the def set and to identify the last use + nodes. + + The itsNotDefSet is built reversed then flipped later. + + */ + private void lookForVariableAccess(OptFunctionNode fn, Node n) + { + switch (n.getType()) { + case Token.DEC : + case Token.INC : + { + Node child = n.getFirstChild(); + if (child.getType() == Token.GETVAR) { + int varIndex = fn.getVarIndex(child); + if (!itsNotDefSet.test(varIndex)) + itsUseBeforeDefSet.set(varIndex); + itsNotDefSet.set(varIndex); + } + } + break; + case Token.SETVAR : + { + Node lhs = n.getFirstChild(); + Node rhs = lhs.getNext(); + lookForVariableAccess(fn, rhs); + itsNotDefSet.set(fn.getVarIndex(n)); + } + break; + case Token.GETVAR : + { + int varIndex = fn.getVarIndex(n); + if (!itsNotDefSet.test(varIndex)) + itsUseBeforeDefSet.set(varIndex); + } + break; + default : + Node child = n.getFirstChild(); + while (child != null) { + lookForVariableAccess(fn, child); + child = child.getNext(); + } + break; + } + } + + /* + build the live on entry/exit sets. + Then walk the trees looking for defs/uses of variables + and build the def and useBeforeDef sets. + */ + private void initLiveOnEntrySets(OptFunctionNode fn, Node[] statementNodes) + { + int listLength = fn.getVarCount(); + itsUseBeforeDefSet = new DataFlowBitSet(listLength); + itsNotDefSet = new DataFlowBitSet(listLength); + itsLiveOnEntrySet = new DataFlowBitSet(listLength); + itsLiveOnExitSet = new DataFlowBitSet(listLength); + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = statementNodes[i]; + lookForVariableAccess(fn, n); + } + itsNotDefSet.not(); // truth in advertising + } + + /* + the liveOnEntry of each successor is the liveOnExit for this block. + The liveOnEntry for this block is - + liveOnEntry = liveOnExit - defsInThisBlock + useBeforeDefsInThisBlock + + */ + private boolean doReachedUseDataFlow() + { + itsLiveOnExitSet.clear(); + if (itsSuccessors != null) + for (int i = 0; i < itsSuccessors.length; i++) + itsLiveOnExitSet.or(itsSuccessors[i].itsLiveOnEntrySet); + return itsLiveOnEntrySet.df2(itsLiveOnExitSet, + itsUseBeforeDefSet, itsNotDefSet); + } + + /* + the type of an expression is relatively unknown. Cases we can be sure + about are - + Literals, + Arithmetic operations - always return a Number + */ + private static int findExpressionType(OptFunctionNode fn, Node n, + int[] varTypes) + { + switch (n.getType()) { + case Token.NUMBER : + return Optimizer.NumberType; + + case Token.CALL : + case Token.NEW : + case Token.REF_CALL : + return Optimizer.AnyType; + + case Token.GETELEM : + return Optimizer.AnyType; + + case Token.GETVAR : + return varTypes[fn.getVarIndex(n)]; + + case Token.INC : + case Token.DEC : + case Token.DIV: + case Token.MOD: + case Token.BITOR: + case Token.BITXOR: + case Token.BITAND: + case Token.LSH: + case Token.RSH: + case Token.URSH: + case Token.SUB : + return Optimizer.NumberType; + + case Token.ARRAYLIT: + case Token.OBJECTLIT: + return Optimizer.AnyType; // XXX: actually, we know it's not + // number, but no type yet for that + + case Token.ADD : { + // if the lhs & rhs are known to be numbers, we can be sure that's + // the result, otherwise it could be a string. + Node child = n.getFirstChild(); + int lType = findExpressionType(fn, child, varTypes); + int rType = findExpressionType(fn, child.getNext(), varTypes); + return lType | rType; // we're not distinguishing strings yet + } + } + + Node child = n.getFirstChild(); + if (child == null) { + return Optimizer.AnyType; + } else { + int result = Optimizer.NoType; + while (child != null) { + result |= findExpressionType(fn, child, varTypes); + child = child.getNext(); + } + return result; + } + } + + private static boolean findDefPoints(OptFunctionNode fn, Node n, + int[] varTypes) + { + boolean result = false; + Node child = n.getFirstChild(); + switch (n.getType()) { + default : + while (child != null) { + result |= findDefPoints(fn, child, varTypes); + child = child.getNext(); + } + break; + case Token.DEC : + case Token.INC : + if (child.getType() == Token.GETVAR) { + // theVar is a Number now + int i = fn.getVarIndex(child); + result |= assignType(varTypes, i, Optimizer.NumberType); + } + break; + case Token.SETPROP : + case Token.SETPROP_OP : + if (child.getType() == Token.GETVAR) { + int i = fn.getVarIndex(child); + assignType(varTypes, i, Optimizer.AnyType); + } + while (child != null) { + result |= findDefPoints(fn, child, varTypes); + child = child.getNext(); + } + break; + case Token.SETVAR : { + Node rValue = child.getNext(); + int theType = findExpressionType(fn, rValue, varTypes); + int i = fn.getVarIndex(n); + result |= assignType(varTypes, i, theType); + break; + } + } + return result; + } + + private boolean doTypeFlow(OptFunctionNode fn, Node[] statementNodes, + int[] varTypes) + { + boolean changed = false; + + for (int i = itsStartNodeIndex; i <= itsEndNodeIndex; i++) { + Node n = statementNodes[i]; + if (n != null) + changed |= findDefPoints(fn, n, varTypes); + } + + return changed; + } + + private void printLiveOnEntrySet(OptFunctionNode fn) + { + if (DEBUG) { + for (int i = 0; i < fn.getVarCount(); i++) { + String name = fn.fnode.getParamOrVarName(i); + if (itsUseBeforeDefSet.test(i)) + System.out.println(name + " is used before def'd"); + if (itsNotDefSet.test(i)) + System.out.println(name + " is not def'd"); + if (itsLiveOnEntrySet.test(i)) + System.out.println(name + " is live on entry"); + if (itsLiveOnExitSet.test(i)) + System.out.println(name + " is live on exit"); + } + } + } + + // all the Blocks that come immediately after this + private Block[] itsSuccessors; + // all the Blocks that come immediately before this + private Block[] itsPredecessors; + + private int itsStartNodeIndex; // the Node at the start of the block + private int itsEndNodeIndex; // the Node at the end of the block + + private int itsBlockID; // a unique index for each block + +// reaching def bit sets - + private DataFlowBitSet itsLiveOnEntrySet; + private DataFlowBitSet itsLiveOnExitSet; + private DataFlowBitSet itsUseBeforeDefSet; + private DataFlowBitSet itsNotDefSet; + + static final boolean DEBUG = false; + private static int debug_blockCount; + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java new file mode 100644 index 0000000..4a69c6a --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/ClassCompiler.java @@ -0,0 +1,214 @@ +/* ***** 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): + * Igor Bukanov + * + * 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.optimizer; + +import org.mozilla.javascript.*; + +/** + * Generates class files from script sources. + * + * since 1.5 Release 5 + * @author Igor Bukanov + */ + +public class ClassCompiler +{ + /** + * Construct ClassCompiler that uses the specified compiler environment + * when generating classes. + */ + public ClassCompiler(CompilerEnvirons compilerEnv) + { + if (compilerEnv == null) throw new IllegalArgumentException(); + this.compilerEnv = compilerEnv; + this.mainMethodClassName = Codegen.DEFAULT_MAIN_METHOD_CLASS; + } + + /** + * Set the class name to use for main method implementation. + * The class must have a method matching + * <tt>public static void main(Script sc, String[] args)</tt>, it will be + * called when <tt>main(String[] args)</tt> is called in the generated + * class. The class name should be fully qulified name and include the + * package name like in <tt>org.foo.Bar<tt>. + */ + public void setMainMethodClass(String className) + { + // XXX Should this check for a valid class name? + mainMethodClassName = className; + } + + /** + * Get the name of the class for main method implementation. + * @see #setMainMethodClass(String) + */ + public String getMainMethodClass() + { + return mainMethodClassName; + } + + /** + * Get the compiler environment the compiler uses. + */ + public CompilerEnvirons getCompilerEnv() + { + return compilerEnv; + } + + /** + * Get the class that the generated target will extend. + */ + public Class getTargetExtends() + { + return targetExtends; + } + + /** + * Set the class that the generated target will extend. + * + * @param extendsClass the class it extends + */ + public void setTargetExtends(Class extendsClass) + { + targetExtends = extendsClass; + } + + /** + * Get the interfaces that the generated target will implement. + */ + public Class[] getTargetImplements() + { + return targetImplements == null ? null : (Class[])targetImplements.clone(); + } + + /** + * Set the interfaces that the generated target will implement. + * + * @param implementsClasses an array of Class objects, one for each + * interface the target will extend + */ + public void setTargetImplements(Class[] implementsClasses) + { + targetImplements = implementsClasses == null ? null : (Class[])implementsClasses.clone(); + } + + /** + * Build class name for a auxiliary class generated by compiler. + * If the compiler needs to generate extra classes beyond the main class, + * it will call this function to build the auxiliary class name. + * The default implementation simply appends auxMarker to mainClassName + * but this can be overridden. + */ + protected String makeAuxiliaryClassName(String mainClassName, + String auxMarker) + { + return mainClassName+auxMarker; + } + + /** + * Compile JavaScript source into one or more Java class files. + * The first compiled class will have name mainClassName. + * If the results of {@link #getTargetExtends()} or + * {@link #getTargetImplements()} are not null, then the first compiled + * class will extend the specified super class and implement + * specified interfaces. + * + * @return array where elements with even indexes specifies class name + * and the following odd index gives class file body as byte[] + * array. The initial element of the array always holds + * mainClassName and array[1] holds its byte code. + */ + public Object[] compileToClassFiles(String source, + String sourceLocation, + int lineno, + String mainClassName) + { + /*APPJET*/ + Parser p = + InformativeParser.makeParser(compilerEnv, + compilerEnv.getErrorReporter()); + ScriptOrFnNode tree = p.parse(source, sourceLocation, lineno); + String encodedSource = p.getEncodedSource(); + + Class superClass = getTargetExtends(); + Class[] interfaces = getTargetImplements(); + String scriptClassName; + boolean isPrimary = (interfaces == null && superClass == null); + if (isPrimary) { + scriptClassName = mainClassName; + } else { + scriptClassName = makeAuxiliaryClassName(mainClassName, "1"); + } + + Codegen codegen = new Codegen(); + codegen.setMainMethodClass(mainMethodClassName); + byte[] scriptClassBytes + = codegen.compileToClassFile(compilerEnv, scriptClassName, + tree, encodedSource, + false); + + if (isPrimary) { + return new Object[] { scriptClassName, scriptClassBytes }; + } + int functionCount = tree.getFunctionCount(); + ObjToIntMap functionNames = new ObjToIntMap(functionCount); + for (int i = 0; i != functionCount; ++i) { + FunctionNode ofn = tree.getFunctionNode(i); + String name = ofn.getFunctionName(); + if (name != null && name.length() != 0) { + functionNames.put(name, ofn.getParamCount()); + } + } + if (superClass == null) { + superClass = ScriptRuntime.ObjectClass; + } + byte[] mainClassBytes + = JavaAdapter.createAdapterCode( + functionNames, mainClassName, + superClass, interfaces, scriptClassName); + + return new Object[] { mainClassName, mainClassBytes, + scriptClassName, scriptClassBytes }; + } + + private String mainMethodClassName; + private CompilerEnvirons compilerEnv; + private Class targetExtends; + private Class[] targetImplements; + +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java new file mode 100644 index 0000000..64952bf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Codegen.java @@ -0,0 +1,5031 @@ +/* ***** 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): + * Norris Boyd + * Kemal Bayram + * Igor Bukanov + * Bob Jervis + * Roger Lawrence + * Andi Vajda + * Hannes Wallnoefer + * + * 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.optimizer; + +import org.mozilla.javascript.*; +import org.mozilla.classfile.*; +import java.util.*; +import java.lang.reflect.Constructor; +import java.util.Hashtable; + +/** + * This class generates code for a given IR tree. + * + * @author Norris Boyd + * @author Roger Lawrence + */ + +public class Codegen implements Evaluator +{ + public void captureStackInfo(RhinoException ex) { + throw new UnsupportedOperationException(); + } + + public String getSourcePositionFromStack(Context cx, int[] linep) { + throw new UnsupportedOperationException(); + } + + public String getPatchedStack(RhinoException ex, String nativeStackTrace) { + throw new UnsupportedOperationException(); + } + + public List getScriptStack(RhinoException ex) { + throw new UnsupportedOperationException(); + } + + public void setEvalScriptFlag(Script script) { + throw new UnsupportedOperationException(); + } + + public Object compile(CompilerEnvirons compilerEnv, + ScriptOrFnNode tree, + String encodedSource, + boolean returnFunction) + { + int serial; + synchronized (globalLock) { + serial = ++globalSerialClassCounter; + } + String mainClassName = "org.mozilla.javascript.gen.c"+serial; + + byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName, + tree, encodedSource, + returnFunction); + + return new Object[] { mainClassName, mainClassBytes }; + } + + public Script createScriptObject(Object bytecode, + Object staticSecurityDomain) + { + Class cl = defineClass(bytecode, staticSecurityDomain); + + Script script; + try { + script = (Script)cl.newInstance(); + } catch (Exception ex) { + throw new RuntimeException + ("Unable to instantiate compiled class:"+ex.toString()); + } + return script; + } + + public Function createFunctionObject(Context cx, Scriptable scope, + Object bytecode, + Object staticSecurityDomain) + { + Class cl = defineClass(bytecode, staticSecurityDomain); + + NativeFunction f; + try { + Constructor ctor = cl.getConstructors()[0]; + Object[] initArgs = { scope, cx, new Integer(0) }; + f = (NativeFunction)ctor.newInstance(initArgs); + } catch (Exception ex) { + throw new RuntimeException + ("Unable to instantiate compiled class:"+ex.toString()); + } + return f; + } + + private Class defineClass(Object bytecode, + Object staticSecurityDomain) + { + Object[] nameBytesPair = (Object[])bytecode; + String className = (String)nameBytesPair[0]; + byte[] classBytes = (byte[])nameBytesPair[1]; + + // The generated classes in this case refer only to Rhino classes + // which must be accessible through this class loader + ClassLoader rhinoLoader = getClass().getClassLoader(); + GeneratedClassLoader loader; + loader = SecurityController.createLoader(rhinoLoader, + staticSecurityDomain); + Exception e; + try { + Class cl = loader.defineClass(className, classBytes); + loader.linkClass(cl); + return cl; + } catch (SecurityException x) { + e = x; + } catch (IllegalArgumentException x) { + e = x; + } + throw new RuntimeException("Malformed optimizer package " + e); + } + + byte[] compileToClassFile(CompilerEnvirons compilerEnv, + String mainClassName, + ScriptOrFnNode scriptOrFn, + String encodedSource, + boolean returnFunction) + { + this.compilerEnv = compilerEnv; + + transform(scriptOrFn); + + if (Token.printTrees) { + /*APPJET*///System.out.println(scriptOrFn.toStringTree(scriptOrFn)); + } + + if (returnFunction) { + scriptOrFn = scriptOrFn.getFunctionNode(0); + } + + initScriptOrFnNodesData(scriptOrFn); + + this.mainClassName = mainClassName; + this.mainClassSignature + = ClassFileWriter.classNameToSignature(mainClassName); + + try { + return generateCode(encodedSource); + } catch (ClassFileWriter.ClassFileFormatException e) { + throw reportClassFileFormatException(scriptOrFn, e.getMessage()); + } + } + + private RuntimeException reportClassFileFormatException( + ScriptOrFnNode scriptOrFn, + String message) + { + String msg = scriptOrFn instanceof FunctionNode + ? ScriptRuntime.getMessage2("msg.while.compiling.fn", + ((FunctionNode)scriptOrFn).getFunctionName(), message) + : ScriptRuntime.getMessage1("msg.while.compiling.script", message); + return Context.reportRuntimeError(msg, scriptOrFn.getSourceName(), + scriptOrFn.getLineno(), null, 0); + } + + private void transform(ScriptOrFnNode tree) + { + initOptFunctions_r(tree); + + int optLevel = compilerEnv.getOptimizationLevel(); + + Hashtable possibleDirectCalls = null; + if (optLevel > 0) { + /* + * Collect all of the contained functions into a hashtable + * so that the call optimizer can access the class name & parameter + * count for any call it encounters + */ + if (tree.getType() == Token.SCRIPT) { + int functionCount = tree.getFunctionCount(); + for (int i = 0; i != functionCount; ++i) { + OptFunctionNode ofn = OptFunctionNode.get(tree, i); + if (ofn.fnode.getFunctionType() + == FunctionNode.FUNCTION_STATEMENT) + { + String name = ofn.fnode.getFunctionName(); + if (name.length() != 0) { + if (possibleDirectCalls == null) { + possibleDirectCalls = new Hashtable(); + } + possibleDirectCalls.put(name, ofn); + } + } + } + } + } + + if (possibleDirectCalls != null) { + directCallTargets = new ObjArray(); + } + + OptTransformer ot = new OptTransformer(possibleDirectCalls, + directCallTargets); + ot.transform(tree); + + if (optLevel > 0) { + (new Optimizer()).optimize(tree); + } + } + + private static void initOptFunctions_r(ScriptOrFnNode scriptOrFn) + { + for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) { + FunctionNode fn = scriptOrFn.getFunctionNode(i); + new OptFunctionNode(fn); + initOptFunctions_r(fn); + } + } + + private void initScriptOrFnNodesData(ScriptOrFnNode scriptOrFn) + { + ObjArray x = new ObjArray(); + collectScriptOrFnNodes_r(scriptOrFn, x); + + int count = x.size(); + scriptOrFnNodes = new ScriptOrFnNode[count]; + x.toArray(scriptOrFnNodes); + + scriptOrFnIndexes = new ObjToIntMap(count); + for (int i = 0; i != count; ++i) { + scriptOrFnIndexes.put(scriptOrFnNodes[i], i); + } + } + + private static void collectScriptOrFnNodes_r(ScriptOrFnNode n, + ObjArray x) + { + x.add(n); + int nestedCount = n.getFunctionCount(); + for (int i = 0; i != nestedCount; ++i) { + collectScriptOrFnNodes_r(n.getFunctionNode(i), x); + } + } + + private byte[] generateCode(String encodedSource) + { + boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT); + boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript); + + String sourceFile = null; + if (compilerEnv.isGenerateDebugInfo()) { + sourceFile = scriptOrFnNodes[0].getSourceName(); + } + + ClassFileWriter cfw = new ClassFileWriter(mainClassName, + SUPER_CLASS_NAME, + sourceFile); + cfw.addField(ID_FIELD_NAME, "I", + ClassFileWriter.ACC_PRIVATE); + cfw.addField(DIRECT_CALL_PARENT_FIELD, mainClassSignature, + ClassFileWriter.ACC_PRIVATE); + cfw.addField(REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE, + ClassFileWriter.ACC_PRIVATE); + + if (hasFunctions) { + generateFunctionConstructor(cfw); + } + + if (hasScript) { + cfw.addInterface("org/mozilla/javascript/Script"); + generateScriptCtor(cfw); + generateMain(cfw); + generateExecute(cfw); + } + + generateCallMethod(cfw); + generateResumeGenerator(cfw); + + generateNativeFunctionOverrides(cfw, encodedSource); + + int count = scriptOrFnNodes.length; + for (int i = 0; i != count; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + + BodyCodegen bodygen = new BodyCodegen(); + bodygen.cfw = cfw; + bodygen.codegen = this; + bodygen.compilerEnv = compilerEnv; + bodygen.scriptOrFn = n; + bodygen.scriptOrFnIndex = i; + + try { + bodygen.generateBodyCode(); + } catch (ClassFileWriter.ClassFileFormatException e) { + throw reportClassFileFormatException(n, e.getMessage()); + } + + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + generateFunctionInit(cfw, ofn); + if (ofn.isTargetOfDirectCall()) { + emitDirectConstructor(cfw, ofn); + } + } + } + + if (directCallTargets != null) { + int N = directCallTargets.size(); + for (int j = 0; j != N; ++j) { + cfw.addField(getDirectTargetFieldName(j), + mainClassSignature, + ClassFileWriter.ACC_PRIVATE); + } + } + + emitRegExpInit(cfw); + emitConstantDudeInitializers(cfw); + + return cfw.toByteArray(); + } + + private void emitDirectConstructor(ClassFileWriter cfw, + OptFunctionNode ofn) + { +/* + we generate .. + Scriptable directConstruct(<directCallArgs>) { + Scriptable newInstance = createObject(cx, scope); + Object val = <body-name>(cx, scope, newInstance, <directCallArgs>); + if (val instanceof Scriptable) { + return (Scriptable) val; + } + return newInstance; + } +*/ + cfw.startMethod(getDirectCtorName(ofn.fnode), + getBodyMethodSignature(ofn.fnode), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + + int argCount = ofn.fnode.getParamCount(); + int firstLocal = (4 + argCount * 3) + 1; + + cfw.addALoad(0); // this + cfw.addALoad(1); // cx + cfw.addALoad(2); // scope + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/BaseFunction", + "createObject", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(firstLocal); + + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(firstLocal); + for (int i = 0; i < argCount; i++) { + cfw.addALoad(4 + (i * 3)); + cfw.addDLoad(5 + (i * 3)); + } + cfw.addALoad(4 + argCount * 3); + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(ofn.fnode), + getBodyMethodSignature(ofn.fnode)); + int exitLabel = cfw.acquireLabel(); + cfw.add(ByteCode.DUP); // make a copy of direct call result + cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable"); + cfw.add(ByteCode.IFEQ, exitLabel); + // cast direct call result + cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable"); + cfw.add(ByteCode.ARETURN); + cfw.markLabel(exitLabel); + + cfw.addALoad(firstLocal); + cfw.add(ByteCode.ARETURN); + + cfw.stopMethod((short)(firstLocal + 1)); + } + + static boolean isGenerator(ScriptOrFnNode node) + { + return (node.getType() == Token.FUNCTION ) && + ((FunctionNode)node).isGenerator(); + } + + // How dispatch to generators works: + // Two methods are generated corresponding to a user-written generator. + // One of these creates a generator object (NativeGenerator), which is + // returned to the user. The other method contains all of the body code + // of the generator. + // When a user calls a generator, the call() method dispatches control to + // to the method that creates the NativeGenerator object. Subsequently when + // the user invokes .next(), .send() or any such method on the generator + // object, the resumeGenerator() below dispatches the call to the + // method corresponding to the generator body. As a matter of convention + // the generator body is given the name of the generator activation function + // appended by "_gen". + private void generateResumeGenerator(ClassFileWriter cfw) + { + boolean hasGenerators = false; + for (int i=0; i < scriptOrFnNodes.length; i++) { + if (isGenerator(scriptOrFnNodes[i])) + hasGenerators = true; + } + + // if there are no generators defined, we don't implement a + // resumeGenerator(). The base class provides a default implementation. + if (!hasGenerators) + return; + + cfw.startMethod("resumeGenerator", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "ILjava/lang/Object;" + + "Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + // load arguments for dispatch to the corresponding *_gen method + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(4); + cfw.addALoad(5); + cfw.addILoad(3); + + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1); + cfw.markTableSwitchDefault(startSwitch); + int endlabel = cfw.acquireLabel(); + + for (int i = 0; i < scriptOrFnNodes.length; i++) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + cfw.markTableSwitchCase(startSwitch, i, (short)6); + if (isGenerator(n)) { + String type = "(" + + mainClassSignature + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;I)Ljava/lang/Object;"; + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(n) + "_gen", + type); + cfw.add(ByteCode.ARETURN); + } else { + cfw.add(ByteCode.GOTO, endlabel); + } + } + + cfw.markLabel(endlabel); + pushUndefined(cfw); + cfw.add(ByteCode.ARETURN); + + + // this method uses as many locals as there are arguments (hence 6) + cfw.stopMethod((short)6); + } + + private void generateCallMethod(ClassFileWriter cfw) + { + cfw.startMethod("call", + "(Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Lorg/mozilla/javascript/Scriptable;" + + "[Ljava/lang/Object;)Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + // Generate code for: + // if (!ScriptRuntime.hasTopCall(cx)) { + // return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args); + // } + + int nonTopCallLabel = cfw.acquireLabel(); + cfw.addALoad(1); //cx + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "hasTopCall", + "(Lorg/mozilla/javascript/Context;" + +")Z"); + cfw.add(ByteCode.IFNE, nonTopCallLabel); + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(3); + cfw.addALoad(4); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "doTopCall", + "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.ARETURN); + cfw.markLabel(nonTopCallLabel); + + // Now generate switch to call the real methods + cfw.addALoad(0); + cfw.addALoad(1); + cfw.addALoad(2); + cfw.addALoad(3); + cfw.addALoad(4); + + int end = scriptOrFnNodes.length; + boolean generateSwitch = (2 <= end); + + int switchStart = 0; + int switchStackTop = 0; + if (generateSwitch) { + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + // do switch from (1, end - 1) mapping 0 to + // the default case + switchStart = cfw.addTableSwitch(1, end - 1); + } + + for (int i = 0; i != end; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + if (generateSwitch) { + if (i == 0) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } else { + cfw.markTableSwitchCase(switchStart, i - 1, + switchStackTop); + } + } + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + if (ofn.isTargetOfDirectCall()) { + int pcount = ofn.fnode.getParamCount(); + if (pcount != 0) { + // loop invariant: + // stack top == arguments array from addALoad4() + for (int p = 0; p != pcount; ++p) { + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(p); + int undefArg = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPLE, undefArg); + // get array[p] + cfw.addALoad(4); + cfw.addPush(p); + cfw.add(ByteCode.AALOAD); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(undefArg); + pushUndefined(cfw); + cfw.markLabel(beyond); + // Only one push + cfw.adjustStackTop(-1); + cfw.addPush(0.0); + // restore invariant + cfw.addALoad(4); + } + } + } + } + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainClassName, + getBodyMethodName(n), + getBodyMethodSignature(n)); + cfw.add(ByteCode.ARETURN); + } + cfw.stopMethod((short)5); + // 5: this, cx, scope, js this, args[] + } + + private void generateMain(ClassFileWriter cfw) + { + cfw.startMethod("main", "([Ljava/lang/String;)V", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_STATIC)); + + // load new ScriptImpl() + cfw.add(ByteCode.NEW, cfw.getClassName()); + cfw.add(ByteCode.DUP); + cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(), + "<init>", "()V"); + // load 'args' + cfw.add(ByteCode.ALOAD_0); + // Call mainMethodClass.main(Script script, String[] args) + cfw.addInvoke(ByteCode.INVOKESTATIC, + mainMethodClass, + "main", + "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V"); + cfw.add(ByteCode.RETURN); + // 1 = String[] args + cfw.stopMethod((short)1); + } + + private void generateExecute(ClassFileWriter cfw) + { + cfw.startMethod("exec", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;", + (short)(ClassFileWriter.ACC_PUBLIC + | ClassFileWriter.ACC_FINAL)); + + final int CONTEXT_ARG = 1; + final int SCOPE_ARG = 2; + + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.ACONST_NULL); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + cfw.getClassName(), + "call", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + + cfw.add(ByteCode.ARETURN); + // 3 = this + context + scope + cfw.stopMethod((short)3); + } + + private void generateScriptCtor(ClassFileWriter cfw) + { + cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC); + + cfw.addLoadThis(); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "<init>", "()V"); + // set id to 0 + cfw.addLoadThis(); + cfw.addPush(0); + cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + cfw.add(ByteCode.RETURN); + // 1 parameter = this + cfw.stopMethod((short)1); + } + + private void generateFunctionConstructor(ClassFileWriter cfw) + { + final int SCOPE_ARG = 1; + final int CONTEXT_ARG = 2; + final int ID_ARG = 3; + + cfw.startMethod("<init>", FUNCTION_CONSTRUCTOR_SIGNATURE, + ClassFileWriter.ACC_PUBLIC); + cfw.addALoad(0); + cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME, + "<init>", "()V"); + + cfw.addLoadThis(); + cfw.addILoad(ID_ARG); + cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I"); + + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + + int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0; + int end = scriptOrFnNodes.length; + if (start == end) throw badTree(); + boolean generateSwitch = (2 <= end - start); + + int switchStart = 0; + int switchStackTop = 0; + if (generateSwitch) { + cfw.addILoad(ID_ARG); + // do switch from (start + 1, end - 1) mapping start to + // the default case + switchStart = cfw.addTableSwitch(start + 1, end - 1); + } + + for (int i = start; i != end; ++i) { + if (generateSwitch) { + if (i == start) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } else { + cfw.markTableSwitchCase(switchStart, i - 1 - start, + switchStackTop); + } + } + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + mainClassName, + getFunctionInitMethodName(ofn), + FUNCTION_INIT_SIGNATURE); + cfw.add(ByteCode.RETURN); + } + + // 4 = this + scope + context + id + cfw.stopMethod((short)4); + } + + private void generateFunctionInit(ClassFileWriter cfw, + OptFunctionNode ofn) + { + final int CONTEXT_ARG = 1; + final int SCOPE_ARG = 2; + cfw.startMethod(getFunctionInitMethodName(ofn), + FUNCTION_INIT_SIGNATURE, + (short)(ClassFileWriter.ACC_PRIVATE + | ClassFileWriter.ACC_FINAL)); + + // Call NativeFunction.initScriptFunction + cfw.addLoadThis(); + cfw.addALoad(CONTEXT_ARG); + cfw.addALoad(SCOPE_ARG); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "org/mozilla/javascript/NativeFunction", + "initScriptFunction", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"); + + // precompile all regexp literals + int regexpCount = ofn.fnode.getRegexpCount(); + if (regexpCount != 0) { + cfw.addLoadThis(); + pushRegExpArray(cfw, ofn.fnode, CONTEXT_ARG, SCOPE_ARG); + cfw.add(ByteCode.PUTFIELD, mainClassName, + REGEXP_ARRAY_FIELD_NAME, REGEXP_ARRAY_FIELD_TYPE); + } + + cfw.add(ByteCode.RETURN); + // 3 = (scriptThis/functionRef) + scope + context + cfw.stopMethod((short)3); + } + + private void generateNativeFunctionOverrides(ClassFileWriter cfw, + String encodedSource) + { + // Override NativeFunction.getLanguageVersion() with + // public int getLanguageVersion() { return <version-constant>; } + + cfw.startMethod("getLanguageVersion", "()I", + ClassFileWriter.ACC_PUBLIC); + + cfw.addPush(compilerEnv.getLanguageVersion()); + cfw.add(ByteCode.IRETURN); + + // 1: this and no argument or locals + cfw.stopMethod((short)1); + + // The rest of NativeFunction overrides require specific code for each + // script/function id + + final int Do_getFunctionName = 0; + final int Do_getParamCount = 1; + final int Do_getParamAndVarCount = 2; + final int Do_getParamOrVarName = 3; + final int Do_getEncodedSource = 4; + final int Do_getParamOrVarConst = 5; + final int SWITCH_COUNT = 6; + + for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) { + if (methodIndex == Do_getEncodedSource && encodedSource == null) { + continue; + } + + // Generate: + // prologue; + // switch over function id to implement function-specific action + // epilogue + + short methodLocals; + switch (methodIndex) { + case Do_getFunctionName: + methodLocals = 1; // Only this + cfw.startMethod("getFunctionName", "()Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamCount: + methodLocals = 1; // Only this + cfw.startMethod("getParamCount", "()I", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamAndVarCount: + methodLocals = 1; // Only this + cfw.startMethod("getParamAndVarCount", "()I", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamOrVarName: + methodLocals = 1 + 1; // this + paramOrVarIndex + cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getParamOrVarConst: + methodLocals = 1 + 1 + 1; // this + paramOrVarName + cfw.startMethod("getParamOrVarConst", "(I)Z", + ClassFileWriter.ACC_PUBLIC); + break; + case Do_getEncodedSource: + methodLocals = 1; // Only this + cfw.startMethod("getEncodedSource", "()Ljava/lang/String;", + ClassFileWriter.ACC_PUBLIC); + cfw.addPush(encodedSource); + break; + default: + throw Kit.codeBug(); + } + + int count = scriptOrFnNodes.length; + + int switchStart = 0; + int switchStackTop = 0; + if (count > 1) { + // Generate switch but only if there is more then one + // script/function + cfw.addLoadThis(); + cfw.add(ByteCode.GETFIELD, cfw.getClassName(), + ID_FIELD_NAME, "I"); + + // do switch from 1 .. count - 1 mapping 0 to the default case + switchStart = cfw.addTableSwitch(1, count - 1); + } + + for (int i = 0; i != count; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + if (i == 0) { + if (count > 1) { + cfw.markTableSwitchDefault(switchStart); + switchStackTop = cfw.getStackTop(); + } + } else { + cfw.markTableSwitchCase(switchStart, i - 1, + switchStackTop); + } + + // Impelemnet method-specific switch code + switch (methodIndex) { + case Do_getFunctionName: + // Push function name + if (n.getType() == Token.SCRIPT) { + cfw.addPush(""); + } else { + String name = ((FunctionNode)n).getFunctionName(); + cfw.addPush(name); + } + cfw.add(ByteCode.ARETURN); + break; + + case Do_getParamCount: + // Push number of defined parameters + cfw.addPush(n.getParamCount()); + cfw.add(ByteCode.IRETURN); + break; + + case Do_getParamAndVarCount: + // Push number of defined parameters and declared variables + cfw.addPush(n.getParamAndVarCount()); + cfw.add(ByteCode.IRETURN); + break; + + case Do_getParamOrVarName: + // Push name of parameter using another switch + // over paramAndVarCount + int paramAndVarCount = n.getParamAndVarCount(); + if (paramAndVarCount == 0) { + // The runtime should never call the method in this + // case but to make bytecode verifier happy return null + // as throwing execption takes more code + cfw.add(ByteCode.ACONST_NULL); + cfw.add(ByteCode.ARETURN); + } else if (paramAndVarCount == 1) { + // As above do not check for valid index but always + // return the name of the first param + cfw.addPush(n.getParamOrVarName(0)); + cfw.add(ByteCode.ARETURN); + } else { + // Do switch over getParamOrVarName + cfw.addILoad(1); // param or var index + // do switch from 1 .. paramAndVarCount - 1 mapping 0 + // to the default case + int paramSwitchStart = cfw.addTableSwitch( + 1, paramAndVarCount - 1); + for (int j = 0; j != paramAndVarCount; ++j) { + if (cfw.getStackTop() != 0) Kit.codeBug(); + String s = n.getParamOrVarName(j); + if (j == 0) { + cfw.markTableSwitchDefault(paramSwitchStart); + } else { + cfw.markTableSwitchCase(paramSwitchStart, j - 1, + 0); + } + cfw.addPush(s); + cfw.add(ByteCode.ARETURN); + } + } + break; + + case Do_getParamOrVarConst: + // Push name of parameter using another switch + // over paramAndVarCount + paramAndVarCount = n.getParamAndVarCount(); + boolean [] constness = n.getParamAndVarConst(); + if (paramAndVarCount == 0) { + // The runtime should never call the method in this + // case but to make bytecode verifier happy return null + // as throwing execption takes more code + cfw.add(ByteCode.ICONST_0); + cfw.add(ByteCode.IRETURN); + } else if (paramAndVarCount == 1) { + // As above do not check for valid index but always + // return the name of the first param + cfw.addPush(constness[0]); + cfw.add(ByteCode.IRETURN); + } else { + // Do switch over getParamOrVarName + cfw.addILoad(1); // param or var index + // do switch from 1 .. paramAndVarCount - 1 mapping 0 + // to the default case + int paramSwitchStart = cfw.addTableSwitch( + 1, paramAndVarCount - 1); + for (int j = 0; j != paramAndVarCount; ++j) { + if (cfw.getStackTop() != 0) Kit.codeBug(); + if (j == 0) { + cfw.markTableSwitchDefault(paramSwitchStart); + } else { + cfw.markTableSwitchCase(paramSwitchStart, j - 1, + 0); + } + cfw.addPush(constness[j]); + cfw.add(ByteCode.IRETURN); + } + } + break; + + case Do_getEncodedSource: + // Push number encoded source start and end + // to prepare for encodedSource.substring(start, end) + cfw.addPush(n.getEncodedSourceStart()); + cfw.addPush(n.getEncodedSourceEnd()); + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, + "java/lang/String", + "substring", + "(II)Ljava/lang/String;"); + cfw.add(ByteCode.ARETURN); + break; + + default: + throw Kit.codeBug(); + } + } + + cfw.stopMethod(methodLocals); + } + } + + private void emitRegExpInit(ClassFileWriter cfw) + { + // precompile all regexp literals + + int totalRegCount = 0; + for (int i = 0; i != scriptOrFnNodes.length; ++i) { + totalRegCount += scriptOrFnNodes[i].getRegexpCount(); + } + if (totalRegCount == 0) { + return; + } + + cfw.startMethod(REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE, + (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_PRIVATE + | ClassFileWriter.ACC_SYNCHRONIZED)); + cfw.addField("_reInitDone", "Z", + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z"); + int doInit = cfw.acquireLabel(); + cfw.add(ByteCode.IFEQ, doInit); + cfw.add(ByteCode.RETURN); + cfw.markLabel(doInit); + + for (int i = 0; i != scriptOrFnNodes.length; ++i) { + ScriptOrFnNode n = scriptOrFnNodes[i]; + int regCount = n.getRegexpCount(); + for (int j = 0; j != regCount; ++j) { + String reFieldName = getCompiledRegexpName(n, j); + String reFieldType = "Ljava/lang/Object;"; + String reString = n.getRegexpString(j); + String reFlags = n.getRegexpFlags(j); + cfw.addField(reFieldName, reFieldType, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + cfw.addALoad(0); // proxy + cfw.addALoad(1); // context + cfw.addPush(reString); + if (reFlags == null) { + cfw.add(ByteCode.ACONST_NULL); + } else { + cfw.addPush(reFlags); + } + /*APPJET*/cfw.addLineNumberEntry((short)n.getRegexpLineno(j)); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/RegExpProxy", + "compileRegExp", + "(Lorg/mozilla/javascript/Context;" + +"Ljava/lang/String;Ljava/lang/String;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.PUTSTATIC, mainClassName, + reFieldName, reFieldType); + } + } + + cfw.addPush(1); + cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z"); + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)2); + } + + private void emitConstantDudeInitializers(ClassFileWriter cfw) + { + int N = itsConstantListSize; + if (N == 0) + return; + + cfw.startMethod("<clinit>", "()V", + (short)(ClassFileWriter.ACC_STATIC | ClassFileWriter.ACC_FINAL)); + + double[] array = itsConstantList; + for (int i = 0; i != N; ++i) { + double num = array[i]; + String constantName = "_k" + i; + String constantType = getStaticConstantWrapperType(num); + cfw.addField(constantName, constantType, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + int inum = (int)num; + if (inum == num) { + cfw.add(ByteCode.NEW, "java/lang/Integer"); + cfw.add(ByteCode.DUP); + cfw.addPush(inum); + cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Integer", + "<init>", "(I)V"); + } else { + cfw.addPush(num); + addDoubleWrap(cfw); + } + cfw.add(ByteCode.PUTSTATIC, mainClassName, + constantName, constantType); + } + + cfw.add(ByteCode.RETURN); + cfw.stopMethod((short)0); + } + + void pushRegExpArray(ClassFileWriter cfw, ScriptOrFnNode n, + int contextArg, int scopeArg) + { + int regexpCount = n.getRegexpCount(); + if (regexpCount == 0) throw badTree(); + + cfw.addPush(regexpCount); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + + cfw.addALoad(contextArg); + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/ScriptRuntime", + "checkRegExpProxy", + "(Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/RegExpProxy;"); + // Stack: proxy, array + cfw.add(ByteCode.DUP); + cfw.addALoad(contextArg); + cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName, + REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE); + for (int i = 0; i != regexpCount; ++i) { + // Stack: proxy, array + cfw.add(ByteCode.DUP2); + cfw.addALoad(contextArg); + cfw.addALoad(scopeArg); + cfw.add(ByteCode.GETSTATIC, mainClassName, + getCompiledRegexpName(n, i), "Ljava/lang/Object;"); + // Stack: compiledRegExp, scope, cx, proxy, array, proxy, array + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/RegExpProxy", + "wrapRegExp", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + // Stack: wrappedRegExp, array, proxy, array + cfw.addPush(i); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.AASTORE); + // Stack: proxy, array + } + // remove proxy + cfw.add(ByteCode.POP); + } + + void pushNumberAsObject(ClassFileWriter cfw, double num) + { + if (num == 0.0) { + if (1 / num > 0) { + // +0.0 + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "zeroObj", "Ljava/lang/Double;"); + } else { + cfw.addPush(num); + addDoubleWrap(cfw); + } + + } else if (num == 1.0) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "oneObj", "Ljava/lang/Double;"); + return; + + } else if (num == -1.0) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "minusOneObj", "Ljava/lang/Double;"); + + } else if (num != num) { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "NaNobj", "Ljava/lang/Double;"); + + } else if (itsConstantListSize >= 2000) { + // There appears to be a limit in the JVM on either the number + // of static fields in a class or the size of the class + // initializer. Either way, we can't have any more than 2000 + // statically init'd constants. + cfw.addPush(num); + addDoubleWrap(cfw); + + } else { + int N = itsConstantListSize; + int index = 0; + if (N == 0) { + itsConstantList = new double[64]; + } else { + double[] array = itsConstantList; + while (index != N && array[index] != num) { + ++index; + } + if (N == array.length) { + array = new double[N * 2]; + System.arraycopy(itsConstantList, 0, array, 0, N); + itsConstantList = array; + } + } + if (index == N) { + itsConstantList[N] = num; + itsConstantListSize = N + 1; + } + String constantName = "_k" + index; + String constantType = getStaticConstantWrapperType(num); + cfw.add(ByteCode.GETSTATIC, mainClassName, + constantName, constantType); + } + } + + private static void addDoubleWrap(ClassFileWriter cfw) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + "wrapDouble", "(D)Ljava/lang/Double;"); + } + + private static String getStaticConstantWrapperType(double num) + { + int inum = (int)num; + if (inum == num) { + return "Ljava/lang/Integer;"; + } else { + return "Ljava/lang/Double;"; + } + } + static void pushUndefined(ClassFileWriter cfw) + { + cfw.add(ByteCode.GETSTATIC, "org/mozilla/javascript/Undefined", + "instance", "Ljava/lang/Object;"); + } + + int getIndex(ScriptOrFnNode n) + { + return scriptOrFnIndexes.getExisting(n); + } + + static String getDirectTargetFieldName(int i) + { + return "_dt" + i; + } + + String getDirectCtorName(ScriptOrFnNode n) + { + return "_n"+getIndex(n); + } + + String getBodyMethodName(ScriptOrFnNode n) + { + return "_c"+getIndex(n); + } + + String getBodyMethodSignature(ScriptOrFnNode n) + { + StringBuffer sb = new StringBuffer(); + sb.append('('); + sb.append(mainClassSignature); + sb.append("Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;"); + if (n.getType() == Token.FUNCTION) { + OptFunctionNode ofn = OptFunctionNode.get(n); + if (ofn.isTargetOfDirectCall()) { + int pCount = ofn.fnode.getParamCount(); + for (int i = 0; i != pCount; i++) { + sb.append("Ljava/lang/Object;D"); + } + } + } + sb.append("[Ljava/lang/Object;)Ljava/lang/Object;"); + return sb.toString(); + } + + String getFunctionInitMethodName(OptFunctionNode ofn) + { + return "_i"+getIndex(ofn.fnode); + } + + String getCompiledRegexpName(ScriptOrFnNode n, int regexpIndex) + { + return "_re"+getIndex(n)+"_"+regexpIndex; + } + + static RuntimeException badTree() + { + throw new RuntimeException("Bad tree in codegen"); + } + + void setMainMethodClass(String className) + { + mainMethodClass = className; + } + + static final String DEFAULT_MAIN_METHOD_CLASS + = "org.mozilla.javascript.optimizer.OptRuntime"; + + private static final String SUPER_CLASS_NAME + = "org.mozilla.javascript.NativeFunction"; + + static final String DIRECT_CALL_PARENT_FIELD = "_dcp"; + private static final String ID_FIELD_NAME = "_id"; + + private static final String REGEXP_INIT_METHOD_NAME = "_reInit"; + private static final String REGEXP_INIT_METHOD_SIGNATURE + = "(Lorg/mozilla/javascript/RegExpProxy;" + +"Lorg/mozilla/javascript/Context;" + +")V"; + static final String REGEXP_ARRAY_FIELD_NAME = "_re"; + static final String REGEXP_ARRAY_FIELD_TYPE = "[Ljava/lang/Object;"; + + static final String FUNCTION_INIT_SIGNATURE + = "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"; + + static final String FUNCTION_CONSTRUCTOR_SIGNATURE + = "(Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;I)V"; + + private static final Object globalLock = new Object(); + private static int globalSerialClassCounter; + + private CompilerEnvirons compilerEnv; + + private ObjArray directCallTargets; + ScriptOrFnNode[] scriptOrFnNodes; + private ObjToIntMap scriptOrFnIndexes; + + private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS; + + String mainClassName; + String mainClassSignature; + + private double[] itsConstantList; + private int itsConstantListSize; +} + + +class BodyCodegen +{ + void generateBodyCode() + { + isGenerator = Codegen.isGenerator(scriptOrFn); + + // generate the body of the current function or script object + initBodyGeneration(); + + if (isGenerator) { + + // All functions in the generated bytecode have a unique name. Every + // generator has a unique prefix followed by _gen + String type = "(" + + codegen.mainClassSignature + + "Lorg/mozilla/javascript/Context;" + + "Lorg/mozilla/javascript/Scriptable;" + + "Ljava/lang/Object;" + + "Ljava/lang/Object;I)Ljava/lang/Object;"; + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn) + "_gen", + type, + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + } else { + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), + codegen.getBodyMethodSignature(scriptOrFn), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + } + + generatePrologue(); + Node treeTop; + if (fnCurrent != null) { + treeTop = scriptOrFn.getLastChild(); + } else { + treeTop = scriptOrFn; + } + generateStatement(treeTop); + generateEpilogue(); + + cfw.stopMethod((short)(localsMax + 1)); + + if (isGenerator) { + // generate the user visible method which when invoked will + // return a generator object + generateGenerator(); + } + } + + // This creates a the user-facing function that returns a NativeGenerator + // object. + private void generateGenerator() + { + cfw.startMethod(codegen.getBodyMethodName(scriptOrFn), + codegen.getBodyMethodSignature(scriptOrFn), + (short)(ClassFileWriter.ACC_STATIC + | ClassFileWriter.ACC_PRIVATE)); + + initBodyGeneration(); + argsLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // get top level scope + if (fnCurrent != null && !inDirectCallFunction + && (!compilerEnv.isUseDynamicScope() + || fnCurrent.fnode.getIgnoreDynamicScope())) + { + // Unless we're either in a direct call or using dynamic scope, + // use the enclosing scope of the function as our variable object. + cfw.addALoad(funObjLocal); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + // generators are forced to have an activation record + cfw.addALoad(funObjLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + addScriptRuntimeInvoke("createFunctionActivation", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + + // create a function object + cfw.add(ByteCode.NEW, codegen.mainClassName); + // Call function constructor + cfw.add(ByteCode.DUP); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + cfw.addPush(scriptOrFnIndex); + cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, + "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE); + + // Init mainScript field + cfw.add(ByteCode.DUP); + if (isTopLevel) Kit.codeBug(); // Only functions can be generators + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + + generateNestedFunctionInits(); + + // create the NativeGenerator object that we return + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addLoadConstant(maxLocals); + cfw.addLoadConstant(maxStack); + addOptRuntimeInvoke("createNativeGenerator", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;II" + +")Lorg/mozilla/javascript/Scriptable;"); + + cfw.add(ByteCode.ARETURN); + cfw.stopMethod((short)(localsMax + 1)); + } + + private void generateNestedFunctionInits() + { + int functionCount = scriptOrFn.getFunctionCount(); + for (int i = 0; i != functionCount; i++) { + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, i); + if (ofn.fnode.getFunctionType() + == FunctionNode.FUNCTION_STATEMENT) + { + visitFunction(ofn, FunctionNode.FUNCTION_STATEMENT); + } + } + } + + private void initBodyGeneration() + { + isTopLevel = (scriptOrFn == codegen.scriptOrFnNodes[0]); + + varRegisters = null; + if (scriptOrFn.getType() == Token.FUNCTION) { + fnCurrent = OptFunctionNode.get(scriptOrFn); + hasVarsInRegs = !fnCurrent.fnode.requiresActivation(); + if (hasVarsInRegs) { + int n = fnCurrent.fnode.getParamAndVarCount(); + if (n != 0) { + varRegisters = new short[n]; + } + } + inDirectCallFunction = fnCurrent.isTargetOfDirectCall(); + if (inDirectCallFunction && !hasVarsInRegs) Codegen.badTree(); + } else { + fnCurrent = null; + hasVarsInRegs = false; + inDirectCallFunction = false; + } + + locals = new int[MAX_LOCALS]; + + funObjLocal = 0; + contextLocal = 1; + variableObjectLocal = 2; + thisObjLocal = 3; + localsMax = (short) 4; // number of parms + "this" + firstFreeLocal = 4; + + popvLocal = -1; + argsLocal = -1; + itsZeroArgArray = -1; + itsOneArgArray = -1; + scriptRegexpLocal = -1; + epilogueLabel = -1; + enterAreaStartLabel = -1; + generatorStateLocal = -1; + } + + /** + * Generate the prologue for a function or script. + */ + private void generatePrologue() + { + if (inDirectCallFunction) { + int directParameterCount = scriptOrFn.getParamCount(); + // 0 is reserved for function Object 'this' + // 1 is reserved for context + // 2 is reserved for parentScope + // 3 is reserved for script 'this' + if (firstFreeLocal != 4) Kit.codeBug(); + for (int i = 0; i != directParameterCount; ++i) { + varRegisters[i] = firstFreeLocal; + // 3 is 1 for Object parm and 2 for double parm + firstFreeLocal += 3; + } + if (!fnCurrent.getParameterNumberContext()) { + // make sure that all parameters are objects + itsForcedObjectParameters = true; + for (int i = 0; i != directParameterCount; ++i) { + short reg = varRegisters[i]; + cfw.addALoad(reg); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isObjectLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, isObjectLabel); + cfw.addDLoad(reg + 1); + addDoubleWrap(); + cfw.addAStore(reg); + cfw.markLabel(isObjectLabel); + } + } + } + + if (fnCurrent != null && !inDirectCallFunction + && (!compilerEnv.isUseDynamicScope() + || fnCurrent.fnode.getIgnoreDynamicScope())) + { + // Unless we're either in a direct call or using dynamic scope, + // use the enclosing scope of the function as our variable object. + cfw.addALoad(funObjLocal); + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + // reserve 'args[]' + argsLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // Generate Generator specific prelude + if (isGenerator) { + + // reserve 'args[]' + operationLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + + // Local 3 is a reference to a GeneratorState object. The rest + // of codegen expects local 3 to be a reference to the thisObj. + // So move the value in local 3 to generatorStateLocal, and load + // the saved thisObj from the GeneratorState object. + cfw.addALoad(thisObjLocal); + generatorStateLocal = firstFreeLocal++; + localsMax = firstFreeLocal; + cfw.add(ByteCode.CHECKCAST, OptRuntime.GeneratorState.CLASS_NAME); + cfw.add(ByteCode.DUP); + cfw.addAStore(generatorStateLocal); + cfw.add(ByteCode.GETFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.thisObj_NAME, + OptRuntime.GeneratorState.thisObj_TYPE); + cfw.addAStore(thisObjLocal); + + if (epilogueLabel == -1) { + epilogueLabel = cfw.acquireLabel(); + } + + ArrayList targets = ((FunctionNode)scriptOrFn).getResumptionPoints(); + if (targets != null) { + // get resumption point + generateGetGeneratorResumptionPoint(); + + // generate dispatch table + generatorSwitch = cfw.addTableSwitch(0, + targets.size() + GENERATOR_START); + generateCheckForThrowOrClose(-1, false, GENERATOR_START); + } + } + + if (fnCurrent == null) { + // See comments in case Token.REGEXP + if (scriptOrFn.getRegexpCount() != 0) { + scriptRegexpLocal = getNewWordLocal(); + codegen.pushRegExpArray(cfw, scriptOrFn, contextLocal, + variableObjectLocal); + cfw.addAStore(scriptRegexpLocal); + } + } + + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + + if (hasVarsInRegs) { + // No need to create activation. Pad arguments if need be. + int parmCount = scriptOrFn.getParamCount(); + if (parmCount > 0 && !inDirectCallFunction) { + // Set up args array + // check length of arguments, pad if need be + cfw.addALoad(argsLocal); + cfw.add(ByteCode.ARRAYLENGTH); + cfw.addPush(parmCount); + int label = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ICMPGE, label); + cfw.addALoad(argsLocal); + cfw.addPush(parmCount); + addScriptRuntimeInvoke("padArguments", + "([Ljava/lang/Object;I" + +")[Ljava/lang/Object;"); + cfw.addAStore(argsLocal); + cfw.markLabel(label); + } + + int paramCount = fnCurrent.fnode.getParamCount(); + int varCount = fnCurrent.fnode.getParamAndVarCount(); + boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst(); + + // REMIND - only need to initialize the vars that don't get a value + // before the next call and are used in the function + short firstUndefVar = -1; + for (int i = 0; i != varCount; ++i) { + short reg = -1; + if (i < paramCount) { + if (!inDirectCallFunction) { + reg = getNewWordLocal(); + cfw.addALoad(argsLocal); + cfw.addPush(i); + cfw.add(ByteCode.AALOAD); + cfw.addAStore(reg); + } + } else if (fnCurrent.isNumberVar(i)) { + reg = getNewWordPairLocal(constDeclarations[i]); + cfw.addPush(0.0); + cfw.addDStore(reg); + } else { + reg = getNewWordLocal(constDeclarations[i]); + if (firstUndefVar == -1) { + Codegen.pushUndefined(cfw); + firstUndefVar = reg; + } else { + cfw.addALoad(firstUndefVar); + } + cfw.addAStore(reg); + } + if (reg >= 0) { + if (constDeclarations[i]) { + cfw.addPush(0); + cfw.addIStore(reg + (fnCurrent.isNumberVar(i) ? 2 : 1)); + } + varRegisters[i] = reg; + } + + // Add debug table entry if we're generating debug info + if (compilerEnv.isGenerateDebugInfo()) { + String name = fnCurrent.fnode.getParamOrVarName(i); + String type = fnCurrent.isNumberVar(i) + ? "D" : "Ljava/lang/Object;"; + int startPC = cfw.getCurrentCodeOffset(); + if (reg < 0) { + reg = varRegisters[i]; + } + cfw.addVariableDescriptor(name, type, startPC, reg); + } + } + + // Skip creating activation object. + return; + } + + // skip creating activation object for the body of a generator. The + // activation record required by a generator has already been created + // in generateGenerator(). + if (isGenerator) + return; + + + String debugVariableName; + if (fnCurrent != null) { + debugVariableName = "activation"; + cfw.addALoad(funObjLocal); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(argsLocal); + addScriptRuntimeInvoke("createFunctionActivation", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("enterActivationFunction", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")V"); + } else { + debugVariableName = "global"; + cfw.addALoad(funObjLocal); + cfw.addALoad(thisObjLocal); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(0); // false to indicate it is not eval script + addScriptRuntimeInvoke("initScript", + "(Lorg/mozilla/javascript/NativeFunction;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Z" + +")V"); + } + + enterAreaStartLabel = cfw.acquireLabel(); + epilogueLabel = cfw.acquireLabel(); + cfw.markLabel(enterAreaStartLabel); + + generateNestedFunctionInits(); + + // default is to generate debug info + if (compilerEnv.isGenerateDebugInfo()) { + cfw.addVariableDescriptor(debugVariableName, + "Lorg/mozilla/javascript/Scriptable;", + cfw.getCurrentCodeOffset(), variableObjectLocal); + } + + if (fnCurrent == null) { + // OPT: use dataflow to prove that this assignment is dead + popvLocal = getNewWordLocal(); + Codegen.pushUndefined(cfw); + cfw.addAStore(popvLocal); + + int linenum = scriptOrFn.getEndLineno(); + if (linenum != -1) + cfw.addLineNumberEntry((short)linenum); + + } else { + if (fnCurrent.itsContainsCalls0) { + itsZeroArgArray = getNewWordLocal(); + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + cfw.addAStore(itsZeroArgArray); + } + if (fnCurrent.itsContainsCalls1) { + itsOneArgArray = getNewWordLocal(); + cfw.addPush(1); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + cfw.addAStore(itsOneArgArray); + } + } + } + + private void generateGetGeneratorResumptionPoint() + { + cfw.addALoad(generatorStateLocal); + cfw.add(ByteCode.GETFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.resumptionPoint_NAME, + OptRuntime.GeneratorState.resumptionPoint_TYPE); + } + + private void generateSetGeneratorResumptionPoint(int nextState) + { + cfw.addALoad(generatorStateLocal); + cfw.addLoadConstant(nextState); + cfw.add(ByteCode.PUTFIELD, + OptRuntime.GeneratorState.CLASS_NAME, + OptRuntime.GeneratorState.resumptionPoint_NAME, + OptRuntime.GeneratorState.resumptionPoint_TYPE); + } + + private void generateGetGeneratorStackState() + { + cfw.addALoad(generatorStateLocal); + addOptRuntimeInvoke("getGeneratorStackState", + "(Ljava/lang/Object;)[Ljava/lang/Object;"); + } + + private void generateEpilogue() + { + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + if (isGenerator) { + // generate locals initialization + HashMap liveLocals = ((FunctionNode)scriptOrFn).getLiveLocals(); + if (liveLocals != null) { + ArrayList nodes = ((FunctionNode)scriptOrFn).getResumptionPoints(); + for (int i = 0; i < nodes.size(); i++) { + Node node = (Node) nodes.get(i); + int[] live = (int [])liveLocals.get(node); + if (live != null) { + cfw.markTableSwitchCase(generatorSwitch, + getNextGeneratorState(node)); + generateGetGeneratorLocalsState(); + for (int j = 0; j < live.length; j++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(j); + cfw.add(ByteCode.AALOAD); + cfw.addAStore(live[j]); + } + cfw.add(ByteCode.POP); + cfw.add(ByteCode.GOTO, getTargetLabel(node)); + } + } + } + + // generate dispatch tables for finally + if (finallys != null) { + Enumeration en = finallys.keys(); + while(en.hasMoreElements()) { + Node n = (Node) en.nextElement(); + if (n.getType() == Token.FINALLY) { + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(n); + // the finally will jump here + cfw.markLabel(ret.tableLabel, (short)1); + + // start generating a dispatch table + int startSwitch = cfw.addTableSwitch(0, + ret.jsrPoints.size() - 1); + int c = 0; + cfw.markTableSwitchDefault(startSwitch); + for (int i = 0; i < ret.jsrPoints.size(); i++) { + // generate gotos back to the JSR location + cfw.markTableSwitchCase(startSwitch, c); + cfw.add(ByteCode.GOTO, + ((Integer)ret.jsrPoints.get(i)).intValue()); + c++; + } + } + } + } + } + + if (epilogueLabel != -1) { + cfw.markLabel(epilogueLabel); + } + + if (hasVarsInRegs) { + cfw.add(ByteCode.ARETURN); + return; + } else if (isGenerator) { + if (((FunctionNode)scriptOrFn).getResumptionPoints() != null) { + cfw.markTableSwitchDefault(generatorSwitch); + } + + // change state for re-entry + generateSetGeneratorResumptionPoint(GENERATOR_TERMINATE); + + // throw StopIteration + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke("throwStopIteration", + "(Ljava/lang/Object;)V"); + + Codegen.pushUndefined(cfw); + cfw.add(ByteCode.ARETURN); + + } else if (fnCurrent == null) { + cfw.addALoad(popvLocal); + cfw.add(ByteCode.ARETURN); + } else { + generateActivationExit(); + cfw.add(ByteCode.ARETURN); + + // Generate catch block to catch all and rethrow to call exit code + // under exception propagation as well. + + int finallyHandler = cfw.acquireLabel(); + cfw.markHandler(finallyHandler); + short exceptionObject = getNewWordLocal(); + cfw.addAStore(exceptionObject); + + // Duplicate generateActivationExit() in the catch block since it + // takes less space then full-featured ByteCode.JSR/ByteCode.RET + generateActivationExit(); + + cfw.addALoad(exceptionObject); + releaseWordLocal(exceptionObject); + // rethrow + cfw.add(ByteCode.ATHROW); + + // mark the handler + cfw.addExceptionHandler(enterAreaStartLabel, epilogueLabel, + finallyHandler, null); // catch any + } + } + + private void generateGetGeneratorLocalsState() { + cfw.addALoad(generatorStateLocal); + addOptRuntimeInvoke("getGeneratorLocalsState", + "(Ljava/lang/Object;)[Ljava/lang/Object;"); + } + + private void generateActivationExit() + { + if (fnCurrent == null || hasVarsInRegs) throw Kit.codeBug(); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("exitActivationFunction", + "(Lorg/mozilla/javascript/Context;)V"); + } + + private void generateStatement(Node node) + { + updateLineNumber(node); + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case Token.LOOP: + case Token.LABEL: + case Token.WITH: + case Token.SCRIPT: + case Token.BLOCK: + case Token.EMPTY: + // no-ops. + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + break; + + case Token.LOCAL_BLOCK: { + int local = getNewWordLocal(); + if (isGenerator) { + cfw.add(ByteCode.ACONST_NULL); + cfw.addAStore(local); + } + node.putIntProp(Node.LOCAL_PROP, local); + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + releaseWordLocal((short)local); + node.removeProp(Node.LOCAL_PROP); + break; + } + + case Token.FUNCTION: { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, fnIndex); + int t = ofn.fnode.getFunctionType(); + if (t == FunctionNode.FUNCTION_EXPRESSION_STATEMENT) { + visitFunction(ofn, t); + } else { + if (t != FunctionNode.FUNCTION_STATEMENT) { + throw Codegen.badTree(); + } + } + break; + } + + case Token.TRY: + visitTryCatchFinally((Node.Jump)node, child); + break; + + case Token.CATCH_SCOPE: + { + // nothing stays on the stack on entry into a catch scope + cfw.setStackTop((short) 0); + + int local = getLocalBlockRegister(node); + int scopeIndex + = node.getExistingIntProp(Node.CATCH_SCOPE_PROP); + + String name = child.getString(); // name of exception + child = child.getNext(); + generateExpression(child, node); // load expression object + if (scopeIndex == 0) { + cfw.add(ByteCode.ACONST_NULL); + } else { + // Load previous catch scope object + cfw.addALoad(local); + } + cfw.addPush(name); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + + addScriptRuntimeInvoke( + "newCatchScope", + "(Ljava/lang/Throwable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(local); + } + break; + + case Token.THROW: + generateExpression(child, node); + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + generateThrowJavaScriptException(); + break; + + case Token.RETHROW: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + cfw.addALoad(getLocalBlockRegister(node)); + cfw.add(ByteCode.ATHROW); + break; + + case Token.RETURN_RESULT: + case Token.RETURN: + if (!isGenerator) { + if (child != null) { + generateExpression(child, node); + } else if (type == Token.RETURN) { + Codegen.pushUndefined(cfw); + } else { + if (popvLocal < 0) throw Codegen.badTree(); + cfw.addALoad(popvLocal); + } + } + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + if (epilogueLabel == -1) { + if (!hasVarsInRegs) throw Codegen.badTree(); + epilogueLabel = cfw.acquireLabel(); + } + cfw.add(ByteCode.GOTO, epilogueLabel); + break; + + case Token.SWITCH: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + visitSwitch((Node.Jump)node, child); + break; + + case Token.ENTERWITH: + generateExpression(child, node); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "enterWith", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + incReferenceWordLocal(variableObjectLocal); + break; + + case Token.LEAVEWITH: + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "leaveWith", + "(Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + decReferenceWordLocal(variableObjectLocal); + break; + + case Token.ENUM_INIT_KEYS: + case Token.ENUM_INIT_VALUES: + case Token.ENUM_INIT_ARRAY: + generateExpression(child, node); + cfw.addALoad(contextLocal); + int enumType = type == Token.ENUM_INIT_KEYS + ? ScriptRuntime.ENUMERATE_KEYS : + type == Token.ENUM_INIT_VALUES + ? ScriptRuntime.ENUMERATE_VALUES : + ScriptRuntime.ENUMERATE_ARRAY; + cfw.addPush(enumType); + addScriptRuntimeInvoke("enumInit", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + cfw.addAStore(getLocalBlockRegister(node)); + break; + + case Token.EXPR_VOID: + if (child.getType() == Token.SETVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetVar(child, child.getFirstChild(), false); + } + else if (child.getType() == Token.SETCONSTVAR) { + /* special case this so as to avoid unnecessary + load's & pop's */ + visitSetConstVar(child, child.getFirstChild(), false); + } + else if (child.getType() == Token.YIELD) { + generateYieldPoint(child, false); + } + else { + generateExpression(child, node); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) + cfw.add(ByteCode.POP2); + else + cfw.add(ByteCode.POP); + } + break; + + case Token.EXPR_RESULT: + generateExpression(child, node); + if (popvLocal < 0) { + popvLocal = getNewWordLocal(); + } + cfw.addAStore(popvLocal); + break; + + case Token.TARGET: + { + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + int label = getTargetLabel(node); + cfw.markLabel(label); + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + } + break; + + case Token.JSR: + case Token.GOTO: + case Token.IFEQ: + case Token.IFNE: + if (compilerEnv.isGenerateObserverCount()) + addInstructionCount(); + visitGoto((Node.Jump)node, type, child); + break; + + case Token.FINALLY: + { + if (compilerEnv.isGenerateObserverCount()) + saveCurrentCodeOffset(); + // there is exactly one value on the stack when enterring + // finally blocks: the return address (or its int encoding) + cfw.setStackTop((short)1); + + // Save return address in a new local + int finallyRegister = getNewWordLocal(); + if (isGenerator) + generateIntegerWrap(); + cfw.addAStore(finallyRegister); + + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + if (isGenerator) { + cfw.addALoad(finallyRegister); + cfw.add(ByteCode.CHECKCAST, "java/lang/Integer"); + generateIntegerUnwrap(); + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(node); + ret.tableLabel = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, ret.tableLabel); + } else { + cfw.add(ByteCode.RET, finallyRegister); + } + releaseWordLocal((short)finallyRegister); + } + break; + + case Token.DEBUGGER: + break; + + default: + throw Codegen.badTree(); + } + + } + + private void generateIntegerWrap() + { + cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/Integer", "valueOf", + "(I)Ljava/lang/Integer;"); + } + + + private void generateIntegerUnwrap() + { + cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/Integer", + "intValue", "()I"); + } + + + private void generateThrowJavaScriptException() + { + cfw.add(ByteCode.NEW, + "org/mozilla/javascript/JavaScriptException"); + cfw.add(ByteCode.DUP_X1); + cfw.add(ByteCode.SWAP); + cfw.addPush(scriptOrFn.getSourceName()); + cfw.addPush(itsLineNumber); + cfw.addInvoke( + ByteCode.INVOKESPECIAL, + "org/mozilla/javascript/JavaScriptException", + "<init>", + "(Ljava/lang/Object;Ljava/lang/String;I)V"); + cfw.add(ByteCode.ATHROW); + } + + private int getNextGeneratorState(Node node) + { + int nodeIndex = ((FunctionNode)scriptOrFn).getResumptionPoints() + .indexOf(node); + return nodeIndex + GENERATOR_YIELD_START; + } + + private void generateExpression(Node node, Node parent) + { + int type = node.getType(); + Node child = node.getFirstChild(); + switch (type) { + case Token.USE_STACK: + break; + + case Token.FUNCTION: + if (fnCurrent != null || parent.getType() != Token.SCRIPT) { + int fnIndex = node.getExistingIntProp(Node.FUNCTION_PROP); + OptFunctionNode ofn = OptFunctionNode.get(scriptOrFn, + fnIndex); + int t = ofn.fnode.getFunctionType(); + if (t != FunctionNode.FUNCTION_EXPRESSION) { + throw Codegen.badTree(); + } + visitFunction(ofn, t); + } + break; + + case Token.NAME: + { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke( + "name", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + break; + + case Token.CALL: + case Token.NEW: + { + int specialType = node.getIntProp(Node.SPECIALCALL_PROP, + Node.NON_SPECIALCALL); + if (specialType == Node.NON_SPECIALCALL) { + OptFunctionNode target; + target = (OptFunctionNode)node.getProp( + Node.DIRECTCALL_PROP); + + if (target != null) { + visitOptimizedCall(node, target, type, child); + } else if (type == Token.CALL) { + visitStandardCall(node, child); + } else { + visitStandardNew(node, child); + } + } else { + visitSpecialCall(node, type, specialType, child); + } + } + break; + + case Token.REF_CALL: + generateFunctionAndThisObj(child, node); + // stack: ... functionObj thisObj + child = child.getNext(); + generateCallArgArray(node, child, false); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "callRef", + "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Ref;"); + break; + + case Token.NUMBER: + { + double num = node.getDouble(); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + cfw.addPush(num); + } else { + codegen.pushNumberAsObject(cfw, num); + } + } + break; + + case Token.STRING: + cfw.addPush(node.getString()); + break; + + case Token.THIS: + cfw.addALoad(thisObjLocal); + break; + + case Token.THISFN: + cfw.add(ByteCode.ALOAD_0); + break; + + case Token.NULL: + cfw.add(ByteCode.ACONST_NULL); + break; + + case Token.TRUE: + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + break; + + case Token.FALSE: + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + break; + + case Token.REGEXP: + { + int i = node.getExistingIntProp(Node.REGEXP_PROP); + // Scripts can not use REGEXP_ARRAY_FIELD_NAME since + // it it will make script.exec non-reentrant so they + // store regexp array in a local variable while + // functions always access precomputed + // REGEXP_ARRAY_FIELD_NAME not to consume locals + if (fnCurrent == null) { + cfw.addALoad(scriptRegexpLocal); + } else { + cfw.addALoad(funObjLocal); + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.REGEXP_ARRAY_FIELD_NAME, + Codegen.REGEXP_ARRAY_FIELD_TYPE); + } + cfw.addPush(i); + cfw.add(ByteCode.AALOAD); + } + break; + + case Token.COMMA: { + Node next = child.getNext(); + while (next != null) { + generateExpression(child, node); + cfw.add(ByteCode.POP); + child = next; + next = next.getNext(); + } + generateExpression(child, node); + break; + } + + case Token.ENUM_NEXT: + case Token.ENUM_ID: { + int local = getLocalBlockRegister(node); + cfw.addALoad(local); + if (type == Token.ENUM_NEXT) { + addScriptRuntimeInvoke( + "enumNext", "(Ljava/lang/Object;)Ljava/lang/Boolean;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("enumId", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + } + + case Token.ARRAYLIT: + visitArrayLiteral(node, child); + break; + + case Token.OBJECTLIT: + visitObjectLiteral(node, child); + break; + + case Token.NOT: { + int trueTarget = cfw.acquireLabel(); + int falseTarget = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + generateIfJump(child, node, trueTarget, falseTarget); + + cfw.markLabel(trueTarget); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(falseTarget); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + cfw.markLabel(beyond); + cfw.adjustStackTop(-1); + break; + } + + case Token.BITNOT: + generateExpression(child, node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + cfw.addPush(-1); // implement ~a as (a ^ -1) + cfw.add(ByteCode.IXOR); + cfw.add(ByteCode.I2D); + addDoubleWrap(); + break; + + case Token.VOID: + generateExpression(child, node); + cfw.add(ByteCode.POP); + Codegen.pushUndefined(cfw); + break; + + case Token.TYPEOF: + generateExpression(child, node); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + break; + + case Token.TYPEOFNAME: + visitTypeofname(node); + break; + + case Token.INC: + case Token.DEC: + visitIncDec(node); + break; + + case Token.OR: + case Token.AND: { + generateExpression(child, node); + cfw.add(ByteCode.DUP); + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)Z"); + int falseTarget = cfw.acquireLabel(); + if (type == Token.AND) + cfw.add(ByteCode.IFEQ, falseTarget); + else + cfw.add(ByteCode.IFNE, falseTarget); + cfw.add(ByteCode.POP); + generateExpression(child.getNext(), node); + cfw.markLabel(falseTarget); + } + break; + + case Token.HOOK : { + Node ifThen = child.getNext(); + Node ifElse = ifThen.getNext(); + generateExpression(child, node); + addScriptRuntimeInvoke("toBoolean", + "(Ljava/lang/Object;)Z"); + int elseTarget = cfw.acquireLabel(); + cfw.add(ByteCode.IFEQ, elseTarget); + short stack = cfw.getStackTop(); + generateExpression(ifThen, node); + int afterHook = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, afterHook); + cfw.markLabel(elseTarget, stack); + generateExpression(ifElse, node); + cfw.markLabel(afterHook); + } + break; + + case Token.ADD: { + generateExpression(child, node); + generateExpression(child.getNext(), node); + switch (node.getIntProp(Node.ISNUMBER_PROP, -1)) { + case Node.BOTH: + cfw.add(ByteCode.DADD); + break; + case Node.LEFT: + addOptRuntimeInvoke("add", + "(DLjava/lang/Object;)Ljava/lang/Object;"); + break; + case Node.RIGHT: + addOptRuntimeInvoke("add", + "(Ljava/lang/Object;D)Ljava/lang/Object;"); + break; + default: + if (child.getType() == Token.STRING) { + addScriptRuntimeInvoke("add", + "(Ljava/lang/String;" + +"Ljava/lang/Object;" + +")Ljava/lang/String;"); + } else if (child.getNext().getType() == Token.STRING) { + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +")Ljava/lang/String;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("add", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + } + break; + + case Token.MUL: + visitArithmetic(node, ByteCode.DMUL, child, parent); + break; + + case Token.SUB: + visitArithmetic(node, ByteCode.DSUB, child, parent); + break; + + case Token.DIV: + case Token.MOD: + visitArithmetic(node, type == Token.DIV + ? ByteCode.DDIV + : ByteCode.DREM, child, parent); + break; + + case Token.BITOR: + case Token.BITXOR: + case Token.BITAND: + case Token.LSH: + case Token.RSH: + case Token.URSH: + visitBitOp(node, type, child); + break; + + case Token.POS: + case Token.NEG: + generateExpression(child, node); + addObjectToDouble(); + if (type == Token.NEG) { + cfw.add(ByteCode.DNEG); + } + addDoubleWrap(); + break; + + case Token.TO_DOUBLE: + // cnvt to double (not Double) + generateExpression(child, node); + addObjectToDouble(); + break; + + case Token.TO_OBJECT: { + // convert from double + int prop = -1; + if (child.getType() == Token.NUMBER) { + prop = child.getIntProp(Node.ISNUMBER_PROP, -1); + } + if (prop != -1) { + child.removeProp(Node.ISNUMBER_PROP); + generateExpression(child, node); + child.putIntProp(Node.ISNUMBER_PROP, prop); + } else { + generateExpression(child, node); + addDoubleWrap(); + } + break; + } + + case Token.IN: + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: { + int trueGOTO = cfw.acquireLabel(); + int falseGOTO = cfw.acquireLabel(); + visitIfJumpRelOp(node, child, trueGOTO, falseGOTO); + addJumpedBooleanWrap(trueGOTO, falseGOTO); + break; + } + + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: { + int trueGOTO = cfw.acquireLabel(); + int falseGOTO = cfw.acquireLabel(); + visitIfJumpEqOp(node, child, trueGOTO, falseGOTO); + addJumpedBooleanWrap(trueGOTO, falseGOTO); + break; + } + + case Token.GETPROP: + case Token.GETPROPNOWARN: + visitGetProp(node, child); + break; + + case Token.GETELEM: + generateExpression(child, node); // object + generateExpression(child.getNext(), node); // id + cfw.addALoad(contextLocal); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addScriptRuntimeInvoke( + "getObjectIndex", + "(Ljava/lang/Object;D" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + else { + addScriptRuntimeInvoke( + "getObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + + case Token.GET_REF: + generateExpression(child, node); // reference + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refGet", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.GETVAR: + visitGetVar(node); + break; + + case Token.SETVAR: + visitSetVar(node, child, true); + break; + + case Token.SETNAME: + visitSetName(node, child); + break; + + case Token.SETCONST: + visitSetConst(node, child); + break; + + case Token.SETCONSTVAR: + visitSetConstVar(node, child, true); + break; + + case Token.SETPROP: + case Token.SETPROP_OP: + visitSetProp(type, node, child); + break; + + case Token.SETELEM: + case Token.SETELEM_OP: + visitSetElem(type, node, child); + break; + + case Token.SET_REF: + case Token.SET_REF_OP: + { + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SET_REF_OP) { + cfw.add(ByteCode.DUP); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refGet", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "refSet", + "(Lorg/mozilla/javascript/Ref;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + break; + + case Token.DEL_REF: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("refDel", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.DELPROP: + generateExpression(child, node); + child = child.getNext(); + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("delete", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.BINDNAME: + { + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + // Generate code for "ScriptRuntime.bind(varObj, "s")" + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke( + "bind", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + break; + + case Token.LOCAL_LOAD: + cfw.addALoad(getLocalBlockRegister(node)); + break; + + case Token.REF_SPECIAL: + { + String special = (String)node.getProp(Node.NAME_PROP); + generateExpression(child, node); + cfw.addPush(special); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "specialRef", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Ref;"); + } + break; + + case Token.REF_MEMBER: + case Token.REF_NS_MEMBER: + case Token.REF_NAME: + case Token.REF_NS_NAME: + { + int memberTypeFlags + = node.getIntProp(Node.MEMBER_TYPE_PROP, 0); + // generate possible target, possible namespace and member + do { + generateExpression(child, node); + child = child.getNext(); + } while (child != null); + cfw.addALoad(contextLocal); + String methodName, signature; + switch (type) { + case Token.REF_MEMBER: + methodName = "memberRef"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + break; + case Token.REF_NS_MEMBER: + methodName = "memberRef"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + break; + case Token.REF_NAME: + methodName = "nameRef"; + signature = "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + cfw.addALoad(variableObjectLocal); + break; + case Token.REF_NS_NAME: + methodName = "nameRef"; + signature = "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" + +")Lorg/mozilla/javascript/Ref;"; + cfw.addALoad(variableObjectLocal); + break; + default: + throw Kit.codeBug(); + } + cfw.addPush(memberTypeFlags); + addScriptRuntimeInvoke(methodName, signature); + } + break; + + case Token.DOTQUERY: + visitDotQuery(node, child); + break; + + case Token.ESCXMLATTR: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("escapeAttributeValue", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/String;"); + break; + + case Token.ESCXMLTEXT: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("escapeTextValue", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/String;"); + break; + + case Token.DEFAULTNAMESPACE: + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke("setDefaultNamespace", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + break; + + case Token.YIELD: + generateYieldPoint(node, true); + break; + + case Token.WITHEXPR: { + Node enterWith = child; + Node with = enterWith.getNext(); + Node leaveWith = with.getNext(); + generateStatement(enterWith); + generateExpression(with.getFirstChild(), with); + generateStatement(leaveWith); + break; + } + + case Token.ARRAYCOMP: { + Node initStmt = child; + Node expr = child.getNext(); + generateStatement(initStmt); + generateExpression(expr, node); + break; + } + + default: + throw new RuntimeException("Unexpected node type "+type); + } + + } + + private void generateYieldPoint(Node node, boolean exprContext) { + // save stack state + int top = cfw.getStackTop(); + maxStack = maxStack > top ? maxStack : top; + if (cfw.getStackTop() != 0) { + generateGetGeneratorStackState(); + for (int i = 0; i < top; i++) { + cfw.add(ByteCode.DUP_X1); + cfw.add(ByteCode.SWAP); + cfw.addLoadConstant(i); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.AASTORE); + } + // pop the array object + cfw.add(ByteCode.POP); + } + + // generate the yield argument + Node child = node.getFirstChild(); + if (child != null) + generateExpression(child, node); + else + Codegen.pushUndefined(cfw); + + // change the resumption state + int nextState = getNextGeneratorState(node); + generateSetGeneratorResumptionPoint(nextState); + + boolean hasLocals = generateSaveLocals(node); + + cfw.add(ByteCode.ARETURN); + + generateCheckForThrowOrClose(getTargetLabel(node), + hasLocals, nextState); + + // reconstruct the stack + if (top != 0) { + generateGetGeneratorStackState(); + for (int i = 0; i < top; i++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(top - i - 1); + cfw.add(ByteCode.AALOAD); + cfw.add(ByteCode.SWAP); + } + cfw.add(ByteCode.POP); + } + + // load return value from yield + if (exprContext) { + cfw.addALoad(argsLocal); + } + } + + private void generateCheckForThrowOrClose(int label, + boolean hasLocals, + int nextState) { + int throwLabel = cfw.acquireLabel(); + int closeLabel = cfw.acquireLabel(); + + // throw the user provided object, if the operation is .throw() + cfw.markLabel(throwLabel); + cfw.addALoad(argsLocal); + generateThrowJavaScriptException(); + + // throw our special internal exception if the generator is being closed + cfw.markLabel(closeLabel); + cfw.addALoad(argsLocal); + cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable"); + cfw.add(ByteCode.ATHROW); + + // mark the re-entry point + // jump here after initializing the locals + if (label != -1) + cfw.markLabel(label); + if (!hasLocals) { + // jump here directly if there are no locals + cfw.markTableSwitchCase(generatorSwitch, nextState); + } + + // see if we need to dispatch for .close() or .throw() + cfw.addILoad(operationLocal); + cfw.addLoadConstant(NativeGenerator.GENERATOR_CLOSE); + cfw.add(ByteCode.IF_ICMPEQ, closeLabel); + cfw.addILoad(operationLocal); + cfw.addLoadConstant(NativeGenerator.GENERATOR_THROW); + cfw.add(ByteCode.IF_ICMPEQ, throwLabel); + } + + private void generateIfJump(Node node, Node parent, + int trueLabel, int falseLabel) + { + // System.out.println("gen code for " + node.toString()); + + int type = node.getType(); + Node child = node.getFirstChild(); + + switch (type) { + case Token.NOT: + generateIfJump(child, node, falseLabel, trueLabel); + break; + + case Token.OR: + case Token.AND: { + int interLabel = cfw.acquireLabel(); + if (type == Token.AND) { + generateIfJump(child, node, interLabel, falseLabel); + } + else { + generateIfJump(child, node, trueLabel, interLabel); + } + cfw.markLabel(interLabel); + child = child.getNext(); + generateIfJump(child, node, trueLabel, falseLabel); + break; + } + + case Token.IN: + case Token.INSTANCEOF: + case Token.LE: + case Token.LT: + case Token.GE: + case Token.GT: + visitIfJumpRelOp(node, child, trueLabel, falseLabel); + break; + + case Token.EQ: + case Token.NE: + case Token.SHEQ: + case Token.SHNE: + visitIfJumpEqOp(node, child, trueLabel, falseLabel); + break; + + default: + // Generate generic code for non-optimized jump + generateExpression(node, parent); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + cfw.add(ByteCode.IFNE, trueLabel); + cfw.add(ByteCode.GOTO, falseLabel); + } + } + + private void visitFunction(OptFunctionNode ofn, int functionType) + { + int fnIndex = codegen.getIndex(ofn.fnode); + cfw.add(ByteCode.NEW, codegen.mainClassName); + // Call function constructor + cfw.add(ByteCode.DUP); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + cfw.addPush(fnIndex); + cfw.addInvoke(ByteCode.INVOKESPECIAL, codegen.mainClassName, + "<init>", Codegen.FUNCTION_CONSTRUCTOR_SIGNATURE); + + // Init mainScript field; + cfw.add(ByteCode.DUP); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + + int directTargetIndex = ofn.getDirectTargetIndex(); + if (directTargetIndex >= 0) { + cfw.add(ByteCode.DUP); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, + codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.PUTFIELD, + codegen.mainClassName, + Codegen.getDirectTargetFieldName(directTargetIndex), + codegen.mainClassSignature); + } + + if (functionType == FunctionNode.FUNCTION_EXPRESSION) { + // Leave closure object on stack and do not pass it to + // initFunction which suppose to connect statements to scope + return; + } + cfw.addPush(functionType); + cfw.addALoad(variableObjectLocal); + cfw.addALoad(contextLocal); // load 'cx' + addOptRuntimeInvoke("initFunction", + "(Lorg/mozilla/javascript/NativeFunction;" + +"I" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +")V"); + } + + private int getTargetLabel(Node target) + { + int labelId = target.labelId(); + if (labelId == -1) { + labelId = cfw.acquireLabel(); + target.labelId(labelId); + } + return labelId; + } + + private void visitGoto(Node.Jump node, int type, Node child) + { + Node target = node.target; + if (type == Token.IFEQ || type == Token.IFNE) { + if (child == null) throw Codegen.badTree(); + int targetLabel = getTargetLabel(target); + int fallThruLabel = cfw.acquireLabel(); + if (type == Token.IFEQ) + generateIfJump(child, node, targetLabel, fallThruLabel); + else + generateIfJump(child, node, fallThruLabel, targetLabel); + cfw.markLabel(fallThruLabel); + } else { + if (type == Token.JSR) { + if (isGenerator) { + addGotoWithReturn(target); + } else { + addGoto(target, ByteCode.JSR); + } + } else { + addGoto(target, ByteCode.GOTO); + } + } + } + + private void addGotoWithReturn(Node target) { + FinallyReturnPoint ret = + (FinallyReturnPoint)finallys.get(target); + cfw.addLoadConstant(ret.jsrPoints.size()); + addGoto(target, ByteCode.GOTO); + int retLabel = cfw.acquireLabel(); + cfw.markLabel(retLabel); + ret.jsrPoints.add(Integer.valueOf(retLabel)); + } + + private void visitArrayLiteral(Node node, Node child) + { + int count = 0; + for (Node cursor = child; cursor != null; cursor = cursor.getNext()) { + ++count; + } + // load array to store array literal objects + addNewObjectArray(count); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + generateExpression(child, node); + cfw.add(ByteCode.AASTORE); + child = child.getNext(); + } + int[] skipIndexes = (int[])node.getProp(Node.SKIP_INDEXES_PROP); + if (skipIndexes == null) { + cfw.add(ByteCode.ACONST_NULL); + cfw.add(ByteCode.ICONST_0); + } else { + cfw.addPush(OptRuntime.encodeIntArray(skipIndexes)); + cfw.addPush(skipIndexes.length); + } + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke("newArrayLiteral", + "([Ljava/lang/Object;" + +"Ljava/lang/String;" + +"I" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitObjectLiteral(Node node, Node child) + { + Object[] properties = (Object[])node.getProp(Node.OBJECT_IDS_PROP); + int count = properties.length; + + // load array with property ids + addNewObjectArray(count); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + Object id = properties[i]; + if (id instanceof String) { + cfw.addPush((String)id); + } else { + cfw.addPush(((Integer)id).intValue()); + addScriptRuntimeInvoke("wrapInt", "(I)Ljava/lang/Integer;"); + } + cfw.add(ByteCode.AASTORE); + } + // load array with property values + addNewObjectArray(count); + Node child2 = child; + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + int childType = child.getType(); + if (childType == Token.GET) { + generateExpression(child.getFirstChild(), node); + } else if (childType == Token.SET) { + generateExpression(child.getFirstChild(), node); + } else { + generateExpression(child, node); + } + cfw.add(ByteCode.AASTORE); + child = child.getNext(); + } + // load array with getterSetter values + cfw.addPush(count); + cfw.add(ByteCode.NEWARRAY, ByteCode.T_INT); + for (int i = 0; i != count; ++i) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + int childType = child2.getType(); + if (childType == Token.GET) { + cfw.add(ByteCode.ICONST_M1); + } else if (childType == Token.SET) { + cfw.add(ByteCode.ICONST_1); + } else { + cfw.add(ByteCode.ICONST_0); + } + cfw.add(ByteCode.IASTORE); + child2 = child2.getNext(); + } + + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("newObjectLiteral", + "([Ljava/lang/Object;" + +"[Ljava/lang/Object;" + +"[I" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitSpecialCall(Node node, int type, int specialType, + Node child) + { + cfw.addALoad(contextLocal); + + if (type == Token.NEW) { + generateExpression(child, node); + // stack: ... cx functionObj + } else { + generateFunctionAndThisObj(child, node); + // stack: ... cx functionObj thisObj + } + child = child.getNext(); + + generateCallArgArray(node, child, false); + + String methodName; + String callSignature; + + if (type == Token.NEW) { + methodName = "newObjectSpecial"; + callSignature = "(Lorg/mozilla/javascript/Context;" + +"Ljava/lang/Object;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" // call type + +")Ljava/lang/Object;"; + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addPush(specialType); + } else { + methodName = "callSpecial"; + callSignature = "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"I" // call type + +"Ljava/lang/String;I" // filename, linenumber + +")Ljava/lang/Object;"; + cfw.addALoad(variableObjectLocal); + cfw.addALoad(thisObjLocal); + cfw.addPush(specialType); + String sourceName = scriptOrFn.getSourceName(); + cfw.addPush(sourceName == null ? "" : sourceName); + cfw.addPush(itsLineNumber); + } + + addOptRuntimeInvoke(methodName, callSignature); + } + + private void visitStandardCall(Node node, Node child) + { + if (node.getType() != Token.CALL) throw Codegen.badTree(); + + Node firstArgChild = child.getNext(); + int childType = child.getType(); + + String methodName; + String signature; + + if (firstArgChild == null) { + if (childType == Token.NAME) { + // name() call + String name = child.getString(); + cfw.addPush(name); + methodName = "callName0"; + signature = "(Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (childType == Token.GETPROP) { + // x.name() call + Node propTarget = child.getFirstChild(); + generateExpression(propTarget, node); + Node id = propTarget.getNext(); + String property = id.getString(); + cfw.addPush(property); + methodName = "callProp0"; + signature = "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (childType == Token.GETPROPNOWARN) { + throw Kit.codeBug(); + } else { + generateFunctionAndThisObj(child, node); + methodName = "call0"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } + + } else if (childType == Token.NAME) { + // XXX: this optimization is only possible if name + // resolution + // is not affected by arguments evaluation and currently + // there are no checks for it + String name = child.getString(); + generateCallArgArray(node, firstArgChild, false); + cfw.addPush(name); + methodName = "callName"; + signature = "([Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else { + int argCount = 0; + for (Node arg = firstArgChild; arg != null; arg = arg.getNext()) { + ++argCount; + } + generateFunctionAndThisObj(child, node); + // stack: ... functionObj thisObj + if (argCount == 1) { + generateExpression(firstArgChild, node); + methodName = "call1"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else if (argCount == 2) { + generateExpression(firstArgChild, node); + generateExpression(firstArgChild.getNext(), node); + methodName = "call2"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } else { + generateCallArgArray(node, firstArgChild, false); + methodName = "callN"; + signature = "(Lorg/mozilla/javascript/Callable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"; + } + } + + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addOptRuntimeInvoke(methodName, signature); + } + + private void visitStandardNew(Node node, Node child) + { + if (node.getType() != Token.NEW) throw Codegen.badTree(); + + Node firstArgChild = child.getNext(); + + generateExpression(child, node); + // stack: ... functionObj + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + // stack: ... functionObj cx scope + generateCallArgArray(node, firstArgChild, false); + addScriptRuntimeInvoke( + "newObject", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void visitOptimizedCall(Node node, OptFunctionNode target, + int type, Node child) + { + Node firstArgChild = child.getNext(); + + short thisObjLocal = 0; + if (type == Token.NEW) { + generateExpression(child, node); + } else { + generateFunctionAndThisObj(child, node); + thisObjLocal = getNewWordLocal(); + cfw.addAStore(thisObjLocal); + } + // stack: ... functionObj + + int beyond = cfw.acquireLabel(); + + int directTargetIndex = target.getDirectTargetIndex(); + if (isTopLevel) { + cfw.add(ByteCode.ALOAD_0); + } else { + cfw.add(ByteCode.ALOAD_0); + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.DIRECT_CALL_PARENT_FIELD, + codegen.mainClassSignature); + } + cfw.add(ByteCode.GETFIELD, codegen.mainClassName, + Codegen.getDirectTargetFieldName(directTargetIndex), + codegen.mainClassSignature); + + cfw.add(ByteCode.DUP2); + // stack: ... functionObj directFunct functionObj directFunct + + int regularCall = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, regularCall); + + // stack: ... functionObj directFunct + short stackHeight = cfw.getStackTop(); + cfw.add(ByteCode.SWAP); + cfw.add(ByteCode.POP); + // stack: ... directFunct + if (compilerEnv.isUseDynamicScope()) { + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + } else { + cfw.add(ByteCode.DUP); + // stack: ... directFunct directFunct + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Scriptable", + "getParentScope", + "()Lorg/mozilla/javascript/Scriptable;"); + // stack: ... directFunct scope + cfw.addALoad(contextLocal); + // stack: ... directFunct scope cx + cfw.add(ByteCode.SWAP); + } + // stack: ... directFunc cx scope + + if (type == Token.NEW) { + cfw.add(ByteCode.ACONST_NULL); + } else { + cfw.addALoad(thisObjLocal); + } + // stack: ... directFunc cx scope thisObj +/* +Remember that directCall parameters are paired in 1 aReg and 1 dReg +If the argument is an incoming arg, just pass the orginal pair thru. +Else, if the argument is known to be typed 'Number', pass Void.TYPE +in the aReg and the number is the dReg +Else pass the JS object in the aReg and 0.0 in the dReg. +*/ + Node argChild = firstArgChild; + while (argChild != null) { + int dcp_register = nodeIsDirectCallParameter(argChild); + if (dcp_register >= 0) { + cfw.addALoad(dcp_register); + cfw.addDLoad(dcp_register + 1); + } else if (argChild.getIntProp(Node.ISNUMBER_PROP, -1) + == Node.BOTH) + { + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + generateExpression(argChild, node); + } else { + generateExpression(argChild, node); + cfw.addPush(0.0); + } + argChild = argChild.getNext(); + } + + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + cfw.addInvoke(ByteCode.INVOKESTATIC, + codegen.mainClassName, + (type == Token.NEW) + ? codegen.getDirectCtorName(target.fnode) + : codegen.getBodyMethodName(target.fnode), + codegen.getBodyMethodSignature(target.fnode)); + + cfw.add(ByteCode.GOTO, beyond); + + cfw.markLabel(regularCall, stackHeight); + // stack: ... functionObj directFunct + cfw.add(ByteCode.POP); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + // stack: ... functionObj cx scope + if (type != Token.NEW) { + cfw.addALoad(thisObjLocal); + releaseWordLocal(thisObjLocal); + // stack: ... functionObj cx scope thisObj + } + // XXX: this will generate code for the child array the second time, + // so expression code generation better not to alter tree structure... + generateCallArgArray(node, firstArgChild, true); + + if (type == Token.NEW) { + addScriptRuntimeInvoke( + "newObject", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Lorg/mozilla/javascript/Scriptable;"); + } else { + cfw.addInvoke(ByteCode.INVOKEINTERFACE, + "org/mozilla/javascript/Callable", + "call", + "(Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Lorg/mozilla/javascript/Scriptable;" + +"[Ljava/lang/Object;" + +")Ljava/lang/Object;"); + } + + cfw.markLabel(beyond); + } + + private void generateCallArgArray(Node node, Node argChild, boolean directCall) + { + int argCount = 0; + for (Node child = argChild; child != null; child = child.getNext()) { + ++argCount; + } + // load array object to set arguments + if (argCount == 1 && itsOneArgArray >= 0) { + cfw.addALoad(itsOneArgArray); + } else { + addNewObjectArray(argCount); + } + // Copy arguments into it + for (int i = 0; i != argCount; ++i) { + // If we are compiling a generator an argument could be the result + // of a yield. In that case we will have an immediate on the stack + // which we need to avoid + if (!isGenerator) { + cfw.add(ByteCode.DUP); + cfw.addPush(i); + } + + if (!directCall) { + generateExpression(argChild, node); + } else { + // If this has also been a directCall sequence, the Number + // flag will have remained set for any parameter so that + // the values could be copied directly into the outgoing + // args. Here we want to force it to be treated as not in + // a Number context, so we set the flag off. + int dcp_register = nodeIsDirectCallParameter(argChild); + if (dcp_register >= 0) { + dcpLoadAsObject(dcp_register); + } else { + generateExpression(argChild, node); + int childNumberFlag + = argChild.getIntProp(Node.ISNUMBER_PROP, -1); + if (childNumberFlag == Node.BOTH) { + addDoubleWrap(); + } + } + } + + // When compiling generators, any argument to a method may be a + // yield expression. Hence we compile the argument first and then + // load the argument index and assign the value to the args array. + if (isGenerator) { + short tempLocal = getNewWordLocal(); + cfw.addAStore(tempLocal); + cfw.add(ByteCode.CHECKCAST, "[Ljava/lang/Object;"); + cfw.add(ByteCode.DUP); + cfw.addPush(i); + cfw.addALoad(tempLocal); + releaseWordLocal(tempLocal); + } + + cfw.add(ByteCode.AASTORE); + + argChild = argChild.getNext(); + } + } + + private void generateFunctionAndThisObj(Node node, Node parent) + { + // Place on stack (function object, function this) pair + int type = node.getType(); + switch (node.getType()) { + case Token.GETPROPNOWARN: + throw Kit.codeBug(); + + case Token.GETPROP: + case Token.GETELEM: { + Node target = node.getFirstChild(); + generateExpression(target, node); + Node id = target.getNext(); + if (type == Token.GETPROP) { + String property = id.getString(); + cfw.addPush(property); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getPropFunctionAndThis", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + } else { + // Optimizer do not optimize this case for now + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) + throw Codegen.badTree(); + generateExpression(id, node); // id + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getElemFunctionAndThis", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + } + break; + } + + case Token.NAME: { + String name = node.getString(); + cfw.addPush(name); + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke( + "getNameFunctionAndThis", + "(Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Callable;"); + break; + } + + default: // including GETVAR + generateExpression(node, parent); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getValueFunctionAndThis", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Callable;"); + break; + } + // Get thisObj prepared by get(Name|Prop|Elem|Value)FunctionAndThis + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "lastStoredScriptable", + "(Lorg/mozilla/javascript/Context;" + +")Lorg/mozilla/javascript/Scriptable;"); + } + + private void updateLineNumber(Node node) + { + itsLineNumber = node.getLineno(); + if (itsLineNumber == -1) + return; + cfw.addLineNumberEntry((short)itsLineNumber); + } + + private void visitTryCatchFinally(Node.Jump node, Node child) + { + /* Save the variable object, in case there are with statements + * enclosed by the try block and we catch some exception. + * We'll restore it for the catch block so that catch block + * statements get the right scope. + */ + + // OPT we only need to do this if there are enclosed WITH + // statements; could statically check and omit this if there aren't any. + + // XXX OPT Maybe instead do syntactic transforms to associate + // each 'with' with a try/finally block that does the exitwith. + + short savedVariableObject = getNewWordLocal(); + cfw.addALoad(variableObjectLocal); + cfw.addAStore(savedVariableObject); + + /* + * Generate the code for the tree; most of the work is done in IRFactory + * and NodeTransformer; Codegen just adds the java handlers for the + * javascript catch and finally clauses. */ + + int startLabel = cfw.acquireLabel(); + cfw.markLabel(startLabel, (short)0); + + Node catchTarget = node.target; + Node finallyTarget = node.getFinally(); + + // create a table for the equivalent of JSR returns + if (isGenerator && finallyTarget != null) { + FinallyReturnPoint ret = new FinallyReturnPoint(); + if (finallys == null) { + finallys = new Hashtable(); + } + // add the finally target to hashtable + finallys.put(finallyTarget, ret); + // add the finally node as well to the hash table + finallys.put(finallyTarget.getNext(), ret); + } + + while (child != null) { + generateStatement(child); + child = child.getNext(); + } + + // control flow skips the handlers + int realEnd = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, realEnd); + + int exceptionLocal = getLocalBlockRegister(node); + // javascript handler; unwrap exception and GOTO to javascript + // catch area. + if (catchTarget != null) { + // get the label to goto + int catchLabel = catchTarget.labelId(); + + generateCatchBlock(JAVASCRIPT_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + /* + * catch WrappedExceptions, see if they are wrapped + * JavaScriptExceptions. Otherwise, rethrow. + */ + generateCatchBlock(EVALUATOR_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + + /* + we also need to catch EcmaErrors and feed the + associated error object to the handler + */ + generateCatchBlock(ECMAERROR_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + + Context cx = Context.getCurrentContext(); + if (cx != null && + cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)) + { + generateCatchBlock(THROWABLE_EXCEPTION, savedVariableObject, + catchLabel, startLabel, exceptionLocal); + } + } + + // finally handler; catch all exceptions, store to a local; JSR to + // the finally, then re-throw. + if (finallyTarget != null) { + int finallyHandler = cfw.acquireLabel(); + cfw.markHandler(finallyHandler); + cfw.addAStore(exceptionLocal); + + // reset the variable object local + cfw.addALoad(savedVariableObject); + cfw.addAStore(variableObjectLocal); + + // get the label to JSR to + int finallyLabel = finallyTarget.labelId(); + if (isGenerator) + addGotoWithReturn(finallyTarget); + else + cfw.add(ByteCode.JSR, finallyLabel); + + // rethrow + cfw.addALoad(exceptionLocal); + if (isGenerator) + cfw.add(ByteCode.CHECKCAST, "java/lang/Throwable"); + cfw.add(ByteCode.ATHROW); + + // mark the handler + cfw.addExceptionHandler(startLabel, finallyLabel, + finallyHandler, null); // catch any + } + releaseWordLocal(savedVariableObject); + cfw.markLabel(realEnd); + } + + private static final int JAVASCRIPT_EXCEPTION = 0; + private static final int EVALUATOR_EXCEPTION = 1; + private static final int ECMAERROR_EXCEPTION = 2; + private static final int THROWABLE_EXCEPTION = 3; + + private void generateCatchBlock(int exceptionType, + short savedVariableObject, + int catchLabel, int startLabel, + int exceptionLocal) + { + int handler = cfw.acquireLabel(); + cfw.markHandler(handler); + + // MS JVM gets cranky if the exception object is left on the stack + cfw.addAStore(exceptionLocal); + + // reset the variable object local + cfw.addALoad(savedVariableObject); + cfw.addAStore(variableObjectLocal); + + String exceptionName; + if (exceptionType == JAVASCRIPT_EXCEPTION) { + exceptionName = "org/mozilla/javascript/JavaScriptException"; + } else if (exceptionType == EVALUATOR_EXCEPTION) { + exceptionName = "org/mozilla/javascript/EvaluatorException"; + } else if (exceptionType == ECMAERROR_EXCEPTION) { + exceptionName = "org/mozilla/javascript/EcmaError"; + } else if (exceptionType == THROWABLE_EXCEPTION) { + exceptionName = "java/lang/Throwable"; + } else { + throw Kit.codeBug(); + } + + // mark the handler + cfw.addExceptionHandler(startLabel, catchLabel, handler, + exceptionName); + + cfw.add(ByteCode.GOTO, catchLabel); + } + + + private boolean generateSaveLocals(Node node) + { + int count = 0; + for (int i = 0; i < firstFreeLocal; i++) { + if (locals[i] != 0) + count++; + } + + if (count == 0) { + ((FunctionNode)scriptOrFn).addLiveLocals(node, null); + return false; + } + + // calculate the max locals + maxLocals = maxLocals > count ? maxLocals : count; + + // create a locals list + int[] ls = new int[count]; + int s = 0; + for (int i = 0; i < firstFreeLocal; i++) { + if (locals[i] != 0) { + ls[s] = i; + s++; + } + } + + // save the locals + ((FunctionNode)scriptOrFn).addLiveLocals(node, ls); + + // save locals + generateGetGeneratorLocalsState(); + for (int i = 0; i < count; i++) { + cfw.add(ByteCode.DUP); + cfw.addLoadConstant(i); + cfw.addALoad(ls[i]); + cfw.add(ByteCode.AASTORE); + } + // pop the array off the stack + cfw.add(ByteCode.POP); + + return true; + } + + private void visitSwitch(Node.Jump switchNode, Node child) + { + // See comments in IRFactory.createSwitch() for description + // of SWITCH node + + generateExpression(child, switchNode); + // save selector value + short selector = getNewWordLocal(); + cfw.addAStore(selector); + + for (Node.Jump caseNode = (Node.Jump)child.getNext(); + caseNode != null; + caseNode = (Node.Jump)caseNode.getNext()) + { + if (caseNode.getType() != Token.CASE) + throw Codegen.badTree(); + Node test = caseNode.getFirstChild(); + generateExpression(test, caseNode); + cfw.addALoad(selector); + addScriptRuntimeInvoke("shallowEq", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + addGoto(caseNode.target, ByteCode.IFNE); + } + releaseWordLocal(selector); + } + + private void visitTypeofname(Node node) + { + if (hasVarsInRegs) { + int varIndex = fnCurrent.fnode.getIndexForNameNode(node); + if (varIndex >= 0) { + if (fnCurrent.isNumberVar(varIndex)) { + cfw.addPush("number"); + } else if (varIsDirectCallParameter(varIndex)) { + int dcp_register = varRegisters[varIndex]; + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, "java/lang/Void", "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addPush("number"); + cfw.markLabel(beyond); + } else { + cfw.addALoad(varRegisters[varIndex]); + addScriptRuntimeInvoke("typeof", + "(Ljava/lang/Object;" + +")Ljava/lang/String;"); + } + return; + } + } + cfw.addALoad(variableObjectLocal); + cfw.addPush(node.getString()); + addScriptRuntimeInvoke("typeofName", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/String;"); + } + + /** + * Save the current code offset. This saved code offset is used to + * compute instruction counts in subsequent calls to + * {@link #addInstructionCount}. + */ + private void saveCurrentCodeOffset() { + savedCodeOffset = cfw.getCurrentCodeOffset(); + } + + /** + * Generate calls to ScriptRuntime.addInstructionCount to keep track of + * executed instructions and call <code>observeInstructionCount()</code> + * if a threshold is exceeded. + */ + private void addInstructionCount() { + int count = cfw.getCurrentCodeOffset() - savedCodeOffset; + if (count == 0) + return; + cfw.addALoad(contextLocal); + cfw.addPush(count); + addScriptRuntimeInvoke("addInstructionCount", + "(Lorg/mozilla/javascript/Context;" + +"I)V"); + } + + private void visitIncDec(Node node) + { + int incrDecrMask = node.getExistingIntProp(Node.INCRDECR_PROP); + Node child = node.getFirstChild(); + switch (child.getType()) { + case Token.GETVAR: + if (!hasVarsInRegs) Kit.codeBug(); + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); + int varIndex = fnCurrent.getVarIndex(child); + short reg = varRegisters[varIndex]; + int offset = varIsDirectCallParameter(varIndex) ? 1 : 0; + cfw.addDLoad(reg + offset); + if (post) { + cfw.add(ByteCode.DUP2); + } + cfw.addPush(1.0); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + cfw.add(ByteCode.DADD); + } else { + cfw.add(ByteCode.DSUB); + } + if (!post) { + cfw.add(ByteCode.DUP2); + } + cfw.addDStore(reg + offset); + } else { + boolean post = ((incrDecrMask & Node.POST_FLAG) != 0); + int varIndex = fnCurrent.getVarIndex(child); + short reg = varRegisters[varIndex]; + cfw.addALoad(reg); + if (post) { + cfw.add(ByteCode.DUP); + } + addObjectToDouble(); + cfw.addPush(1.0); + if ((incrDecrMask & Node.DECR_FLAG) == 0) { + cfw.add(ByteCode.DADD); + } else { + cfw.add(ByteCode.DSUB); + } + addDoubleWrap(); + if (!post) { + cfw.add(ByteCode.DUP); + } + cfw.addAStore(reg); + break; + } + break; + case Token.NAME: + cfw.addALoad(variableObjectLocal); + cfw.addPush(child.getString()); // push name + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke("nameIncrDecr", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + case Token.GETPROPNOWARN: + throw Kit.codeBug(); + case Token.GETPROP: { + Node getPropChild = child.getFirstChild(); + generateExpression(getPropChild, node); + generateExpression(getPropChild.getNext(), node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke("propIncrDecr", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + } + case Token.GETELEM: { + Node elemChild = child.getFirstChild(); + generateExpression(elemChild, node); + generateExpression(elemChild.getNext(), node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + if (elemChild.getNext().getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + addOptRuntimeInvoke("elemIncrDecr", + "(Ljava/lang/Object;" + +"D" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + } else { + addScriptRuntimeInvoke("elemIncrDecr", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"I" + +")Ljava/lang/Object;"); + } + break; + } + case Token.GET_REF: { + Node refChild = child.getFirstChild(); + generateExpression(refChild, node); + cfw.addALoad(contextLocal); + cfw.addPush(incrDecrMask); + addScriptRuntimeInvoke( + "refIncrDecr", + "(Lorg/mozilla/javascript/Ref;" + +"Lorg/mozilla/javascript/Context;" + +"I)Ljava/lang/Object;"); + break; + } + default: + Codegen.badTree(); + } + } + + private static boolean isArithmeticNode(Node node) + { + int type = node.getType(); + return (type == Token.SUB) + || (type == Token.MOD) + || (type == Token.DIV) + || (type == Token.MUL); + } + + private void visitArithmetic(Node node, int opCode, Node child, + Node parent) + { + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + if (childNumberFlag != -1) { + generateExpression(child, node); + generateExpression(child.getNext(), node); + cfw.add(opCode); + } + else { + boolean childOfArithmetic = isArithmeticNode(parent); + generateExpression(child, node); + if (!isArithmeticNode(child)) + addObjectToDouble(); + generateExpression(child.getNext(), node); + if (!isArithmeticNode(child.getNext())) + addObjectToDouble(); + cfw.add(opCode); + if (!childOfArithmetic) { + addDoubleWrap(); + } + } + } + + private void visitBitOp(Node node, int type, Node child) + { + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + generateExpression(child, node); + + // special-case URSH; work with the target arg as a long, so + // that we can return a 32-bit unsigned value, and call + // toUint32 instead of toInt32. + if (type == Token.URSH) { + addScriptRuntimeInvoke("toUint32", "(Ljava/lang/Object;)J"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + // Looks like we need to explicitly mask the shift to 5 bits - + // LUSHR takes 6 bits. + cfw.addPush(31); + cfw.add(ByteCode.IAND); + cfw.add(ByteCode.LUSHR); + cfw.add(ByteCode.L2D); + addDoubleWrap(); + return; + } + if (childNumberFlag == -1) { + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(Ljava/lang/Object;)I"); + } + else { + addScriptRuntimeInvoke("toInt32", "(D)I"); + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toInt32", "(D)I"); + } + switch (type) { + case Token.BITOR: + cfw.add(ByteCode.IOR); + break; + case Token.BITXOR: + cfw.add(ByteCode.IXOR); + break; + case Token.BITAND: + cfw.add(ByteCode.IAND); + break; + case Token.RSH: + cfw.add(ByteCode.ISHR); + break; + case Token.LSH: + cfw.add(ByteCode.ISHL); + break; + default: + throw Codegen.badTree(); + } + cfw.add(ByteCode.I2D); + if (childNumberFlag == -1) { + addDoubleWrap(); + } + } + + private int nodeIsDirectCallParameter(Node node) + { + if (node.getType() == Token.GETVAR + && inDirectCallFunction && !itsForcedObjectParameters) + { + int varIndex = fnCurrent.getVarIndex(node); + if (fnCurrent.isParameter(varIndex)) { + return varRegisters[varIndex]; + } + } + return -1; + } + + private boolean varIsDirectCallParameter(int varIndex) + { + return fnCurrent.isParameter(varIndex) + && inDirectCallFunction && !itsForcedObjectParameters; + } + + private void genSimpleCompare(int type, int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1) throw Codegen.badTree(); + switch (type) { + case Token.LE : + cfw.add(ByteCode.DCMPG); + cfw.add(ByteCode.IFLE, trueGOTO); + break; + case Token.GE : + cfw.add(ByteCode.DCMPL); + cfw.add(ByteCode.IFGE, trueGOTO); + break; + case Token.LT : + cfw.add(ByteCode.DCMPG); + cfw.add(ByteCode.IFLT, trueGOTO); + break; + case Token.GT : + cfw.add(ByteCode.DCMPL); + cfw.add(ByteCode.IFGT, trueGOTO); + break; + default : + throw Codegen.badTree(); + + } + if (falseGOTO != -1) + cfw.add(ByteCode.GOTO, falseGOTO); + } + + private void visitIfJumpRelOp(Node node, Node child, + int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree(); + int type = node.getType(); + Node rChild = child.getNext(); + if (type == Token.INSTANCEOF || type == Token.IN) { + generateExpression(child, node); + generateExpression(rChild, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + (type == Token.INSTANCEOF) ? "instanceOf" : "in", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Z"); + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + return; + } + int childNumberFlag = node.getIntProp(Node.ISNUMBER_PROP, -1); + int left_dcp_register = nodeIsDirectCallParameter(child); + int right_dcp_register = nodeIsDirectCallParameter(rChild); + if (childNumberFlag != -1) { + // Force numeric context on both parameters and optimize + // direct call case as Optimizer currently does not handle it + + if (childNumberFlag != Node.RIGHT) { + // Left already has number content + generateExpression(child, node); + } else if (left_dcp_register != -1) { + dcpLoadAsNumber(left_dcp_register); + } else { + generateExpression(child, node); + addObjectToDouble(); + } + + if (childNumberFlag != Node.LEFT) { + // Right already has number content + generateExpression(rChild, node); + } else if (right_dcp_register != -1) { + dcpLoadAsNumber(right_dcp_register); + } else { + generateExpression(rChild, node); + addObjectToDouble(); + } + + genSimpleCompare(type, trueGOTO, falseGOTO); + + } else { + if (left_dcp_register != -1 && right_dcp_register != -1) { + // Generate code to dynamically check for number content + // if both operands are dcp + short stack = cfw.getStackTop(); + int leftIsNotNumber = cfw.acquireLabel(); + cfw.addALoad(left_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + cfw.add(ByteCode.IF_ACMPNE, leftIsNotNumber); + cfw.addDLoad(left_dcp_register + 1); + dcpLoadAsNumber(right_dcp_register); + genSimpleCompare(type, trueGOTO, falseGOTO); + if (stack != cfw.getStackTop()) throw Codegen.badTree(); + + cfw.markLabel(leftIsNotNumber); + int rightIsNotNumber = cfw.acquireLabel(); + cfw.addALoad(right_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + cfw.add(ByteCode.IF_ACMPNE, rightIsNotNumber); + cfw.addALoad(left_dcp_register); + addObjectToDouble(); + cfw.addDLoad(right_dcp_register + 1); + genSimpleCompare(type, trueGOTO, falseGOTO); + if (stack != cfw.getStackTop()) throw Codegen.badTree(); + + cfw.markLabel(rightIsNotNumber); + // Load both register as objects to call generic cmp_* + cfw.addALoad(left_dcp_register); + cfw.addALoad(right_dcp_register); + + } else { + generateExpression(child, node); + generateExpression(rChild, node); + } + + if (type == Token.GE || type == Token.GT) { + cfw.add(ByteCode.SWAP); + } + String routine = ((type == Token.LT) + || (type == Token.GT)) ? "cmp_LT" : "cmp_LE"; + addScriptRuntimeInvoke(routine, + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + } + } + + private void visitIfJumpEqOp(Node node, Node child, + int trueGOTO, int falseGOTO) + { + if (trueGOTO == -1 || falseGOTO == -1) throw Codegen.badTree(); + + short stackInitial = cfw.getStackTop(); + int type = node.getType(); + Node rChild = child.getNext(); + + // Optimize if one of operands is null + if (child.getType() == Token.NULL || rChild.getType() == Token.NULL) { + // eq is symmetric in this case + if (child.getType() == Token.NULL) { + child = rChild; + } + generateExpression(child, node); + if (type == Token.SHEQ || type == Token.SHNE) { + int testCode = (type == Token.SHEQ) + ? ByteCode.IFNULL : ByteCode.IFNONNULL; + cfw.add(testCode, trueGOTO); + } else { + if (type != Token.EQ) { + // swap false/true targets for != + if (type != Token.NE) throw Codegen.badTree(); + int tmp = trueGOTO; + trueGOTO = falseGOTO; + falseGOTO = tmp; + } + cfw.add(ByteCode.DUP); + int undefCheckLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IFNONNULL, undefCheckLabel); + short stack = cfw.getStackTop(); + cfw.add(ByteCode.POP); + cfw.add(ByteCode.GOTO, trueGOTO); + cfw.markLabel(undefCheckLabel, stack); + Codegen.pushUndefined(cfw); + cfw.add(ByteCode.IF_ACMPEQ, trueGOTO); + } + cfw.add(ByteCode.GOTO, falseGOTO); + } else { + int child_dcp_register = nodeIsDirectCallParameter(child); + if (child_dcp_register != -1 + && rChild.getType() == Token.TO_OBJECT) + { + Node convertChild = rChild.getFirstChild(); + if (convertChild.getType() == Token.NUMBER) { + cfw.addALoad(child_dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int notNumbersLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPNE, notNumbersLabel); + cfw.addDLoad(child_dcp_register + 1); + cfw.addPush(convertChild.getDouble()); + cfw.add(ByteCode.DCMPL); + if (type == Token.EQ) + cfw.add(ByteCode.IFEQ, trueGOTO); + else + cfw.add(ByteCode.IFNE, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + cfw.markLabel(notNumbersLabel); + // fall thru into generic handling + } + } + + generateExpression(child, node); + generateExpression(rChild, node); + + String name; + int testCode; + switch (type) { + case Token.EQ: + name = "eq"; + testCode = ByteCode.IFNE; + break; + case Token.NE: + name = "eq"; + testCode = ByteCode.IFEQ; + break; + case Token.SHEQ: + name = "shallowEq"; + testCode = ByteCode.IFNE; + break; + case Token.SHNE: + name = "shallowEq"; + testCode = ByteCode.IFEQ; + break; + default: + throw Codegen.badTree(); + } + addScriptRuntimeInvoke(name, + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +")Z"); + cfw.add(testCode, trueGOTO); + cfw.add(ByteCode.GOTO, falseGOTO); + } + if (stackInitial != cfw.getStackTop()) throw Codegen.badTree(); + } + + private void visitSetName(Node node, Node child) + { + String name = node.getFirstChild().getString(); + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + cfw.addALoad(contextLocal); + cfw.addALoad(variableObjectLocal); + cfw.addPush(name); + addScriptRuntimeInvoke( + "setName", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + + private void visitSetConst(Node node, Node child) + { + String name = node.getFirstChild().getString(); + while (child != null) { + generateExpression(child, node); + child = child.getNext(); + } + cfw.addALoad(contextLocal); + cfw.addPush(name); + addScriptRuntimeInvoke( + "setConst", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +"Ljava/lang/String;" + +")Ljava/lang/Object;"); + } + + private void visitGetVar(Node node) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + short reg = varRegisters[varIndex]; + if (varIsDirectCallParameter(varIndex)) { + // Remember that here the isNumber flag means that we + // want to use the incoming parameter in a Number + // context, so test the object type and convert the + // value as necessary. + if (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1) { + dcpLoadAsNumber(reg); + } else { + dcpLoadAsObject(reg); + } + } else if (fnCurrent.isNumberVar(varIndex)) { + cfw.addDLoad(reg); + } else { + cfw.addALoad(reg); + } + } + + private void visitSetVar(Node node, Node child, boolean needValue) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + generateExpression(child.getNext(), node); + boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + short reg = varRegisters[varIndex]; + boolean [] constDeclarations = fnCurrent.fnode.getParamAndVarConst(); + if (constDeclarations[varIndex]) { + if (!needValue) { + if (isNumber) + cfw.add(ByteCode.POP2); + else + cfw.add(ByteCode.POP); + } + } + else if (varIsDirectCallParameter(varIndex)) { + if (isNumber) { + if (needValue) cfw.add(ByteCode.DUP2); + cfw.addALoad(reg); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + addDoubleWrap(); + cfw.addAStore(reg); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDStore(reg + 1); + cfw.markLabel(beyond); + } + else { + if (needValue) cfw.add(ByteCode.DUP); + cfw.addAStore(reg); + } + } else { + boolean isNumberVar = fnCurrent.isNumberVar(varIndex); + if (isNumber) { + if (isNumberVar) { + cfw.addDStore(reg); + if (needValue) cfw.addDLoad(reg); + } else { + if (needValue) cfw.add(ByteCode.DUP2); + // Cannot save number in variable since !isNumberVar, + // so convert to object + addDoubleWrap(); + cfw.addAStore(reg); + } + } else { + if (isNumberVar) Kit.codeBug(); + cfw.addAStore(reg); + if (needValue) cfw.addALoad(reg); + } + } + } + + private void visitSetConstVar(Node node, Node child, boolean needValue) + { + if (!hasVarsInRegs) Kit.codeBug(); + int varIndex = fnCurrent.getVarIndex(node); + generateExpression(child.getNext(), node); + boolean isNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + short reg = varRegisters[varIndex]; + int beyond = cfw.acquireLabel(); + int noAssign = cfw.acquireLabel(); + if (isNumber) { + cfw.addILoad(reg + 2); + cfw.add(ByteCode.IFNE, noAssign); + short stack = cfw.getStackTop(); + cfw.addPush(1); + cfw.addIStore(reg + 2); + cfw.addDStore(reg); + if (needValue) { + cfw.addDLoad(reg); + cfw.markLabel(noAssign, stack); + } else { + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(noAssign, stack); + cfw.add(ByteCode.POP2); + } + } + else { + cfw.addILoad(reg + 1); + cfw.add(ByteCode.IFNE, noAssign); + short stack = cfw.getStackTop(); + cfw.addPush(1); + cfw.addIStore(reg + 1); + cfw.addAStore(reg); + if (needValue) { + cfw.addALoad(reg); + cfw.markLabel(noAssign, stack); + } else { + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(noAssign, stack); + cfw.add(ByteCode.POP); + } + } + cfw.markLabel(beyond); + } + + private void visitGetProp(Node node, Node child) + { + generateExpression(child, node); // object + Node nameChild = child.getNext(); + generateExpression(nameChild, node); // the name + if (node.getType() == Token.GETPROPNOWARN) { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectPropNoWarn", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + return; + } + /* + for 'this.foo' we call getObjectProp(Scriptable...) which can + skip some casting overhead. + */ + int childType = child.getType(); + if (childType == Token.THIS && nameChild.getType() == Token.STRING) { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + + private void visitSetProp(int type, Node node, Node child) + { + Node objectChild = child; + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETPROP_OP) { + cfw.add(ByteCode.DUP); + } + Node nameChild = child; + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETPROP_OP) { + // stack: ... object object name -> ... object name object name + cfw.add(ByteCode.DUP_X1); + //for 'this.foo += ...' we call thisGet which can skip some + //casting overhead. + if (objectChild.getType() == Token.THIS + && nameChild.getType() == Token.STRING) + { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Lorg/mozilla/javascript/Scriptable;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "setObjectProp", + "(Ljava/lang/Object;" + +"Ljava/lang/String;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + + private void visitSetElem(int type, Node node, Node child) + { + generateExpression(child, node); + child = child.getNext(); + if (type == Token.SETELEM_OP) { + cfw.add(ByteCode.DUP); + } + generateExpression(child, node); + child = child.getNext(); + boolean indexIsNumber = (node.getIntProp(Node.ISNUMBER_PROP, -1) != -1); + if (type == Token.SETELEM_OP) { + if (indexIsNumber) { + // stack: ... object object number + // -> ... object number object number + cfw.add(ByteCode.DUP2_X1); + cfw.addALoad(contextLocal); + addOptRuntimeInvoke( + "getObjectIndex", + "(Ljava/lang/Object;D" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + // stack: ... object object indexObject + // -> ... object indexObject object indexObject + cfw.add(ByteCode.DUP_X1); + cfw.addALoad(contextLocal); + addScriptRuntimeInvoke( + "getObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + generateExpression(child, node); + cfw.addALoad(contextLocal); + if (indexIsNumber) { + addScriptRuntimeInvoke( + "setObjectIndex", + "(Ljava/lang/Object;" + +"D" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } else { + addScriptRuntimeInvoke( + "setObjectElem", + "(Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Context;" + +")Ljava/lang/Object;"); + } + } + + private void visitDotQuery(Node node, Node child) + { + updateLineNumber(node); + generateExpression(child, node); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("enterDotQuery", + "(Ljava/lang/Object;" + +"Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + + // add push null/pop with label in between to simplify code for loop + // continue when it is necessary to pop the null result from + // updateDotQuery + cfw.add(ByteCode.ACONST_NULL); + int queryLoopStart = cfw.acquireLabel(); + cfw.markLabel(queryLoopStart); // loop continue jumps here + cfw.add(ByteCode.POP); + + generateExpression(child.getNext(), node); + addScriptRuntimeInvoke("toBoolean", "(Ljava/lang/Object;)Z"); + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("updateDotQuery", + "(Z" + +"Lorg/mozilla/javascript/Scriptable;" + +")Ljava/lang/Object;"); + cfw.add(ByteCode.DUP); + cfw.add(ByteCode.IFNULL, queryLoopStart); + // stack: ... non_null_result_of_updateDotQuery + cfw.addALoad(variableObjectLocal); + addScriptRuntimeInvoke("leaveDotQuery", + "(Lorg/mozilla/javascript/Scriptable;" + +")Lorg/mozilla/javascript/Scriptable;"); + cfw.addAStore(variableObjectLocal); + } + + private int getLocalBlockRegister(Node node) + { + Node localBlock = (Node)node.getProp(Node.LOCAL_BLOCK_PROP); + int localSlot = localBlock.getExistingIntProp(Node.LOCAL_PROP); + return localSlot; + } + + private void dcpLoadAsNumber(int dcp_register) + { + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + addObjectToDouble(); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDLoad(dcp_register + 1); + cfw.markLabel(beyond); + } + + private void dcpLoadAsObject(int dcp_register) + { + cfw.addALoad(dcp_register); + cfw.add(ByteCode.GETSTATIC, + "java/lang/Void", + "TYPE", + "Ljava/lang/Class;"); + int isNumberLabel = cfw.acquireLabel(); + cfw.add(ByteCode.IF_ACMPEQ, isNumberLabel); + short stack = cfw.getStackTop(); + cfw.addALoad(dcp_register); + int beyond = cfw.acquireLabel(); + cfw.add(ByteCode.GOTO, beyond); + cfw.markLabel(isNumberLabel, stack); + cfw.addDLoad(dcp_register + 1); + addDoubleWrap(); + cfw.markLabel(beyond); + } + + private void addGoto(Node target, int jumpcode) + { + int targetLabel = getTargetLabel(target); + cfw.add(jumpcode, targetLabel); + } + + private void addObjectToDouble() + { + addScriptRuntimeInvoke("toNumber", "(Ljava/lang/Object;)D"); + } + + private void addNewObjectArray(int size) + { + if (size == 0) { + if (itsZeroArgArray >= 0) { + cfw.addALoad(itsZeroArgArray); + } else { + cfw.add(ByteCode.GETSTATIC, + "org/mozilla/javascript/ScriptRuntime", + "emptyArgs", "[Ljava/lang/Object;"); + } + } else { + cfw.addPush(size); + cfw.add(ByteCode.ANEWARRAY, "java/lang/Object"); + } + } + + private void addScriptRuntimeInvoke(String methodName, + String methodSignature) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org.mozilla.javascript.ScriptRuntime", + methodName, + methodSignature); + } + + private void addOptRuntimeInvoke(String methodName, + String methodSignature) + { + cfw.addInvoke(ByteCode.INVOKESTATIC, + "org/mozilla/javascript/optimizer/OptRuntime", + methodName, + methodSignature); + } + + private void addJumpedBooleanWrap(int trueLabel, int falseLabel) + { + cfw.markLabel(falseLabel); + int skip = cfw.acquireLabel(); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "FALSE", "Ljava/lang/Boolean;"); + cfw.add(ByteCode.GOTO, skip); + cfw.markLabel(trueLabel); + cfw.add(ByteCode.GETSTATIC, "java/lang/Boolean", + "TRUE", "Ljava/lang/Boolean;"); + cfw.markLabel(skip); + cfw.adjustStackTop(-1); // only have 1 of true/false + } + + private void addDoubleWrap() + { + addOptRuntimeInvoke("wrapDouble", "(D)Ljava/lang/Double;"); + } + + /** + * Const locals use an extra slot to hold the has-been-assigned-once flag at + * runtime. + * @param isConst true iff the variable is const + * @return the register for the word pair (double/long) + */ + private short getNewWordPairLocal(boolean isConst) + { + short result = getConsecutiveSlots(2, isConst); + if (result < (MAX_LOCALS - 1)) { + locals[result] = 1; + locals[result + 1] = 1; + if (isConst) + locals[result + 2] = 1; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getNewWordLocal(boolean isConst) + { + short result = getConsecutiveSlots(1, isConst); + if (result < (MAX_LOCALS - 1)) { + locals[result] = 1; + if (isConst) + locals[result + 1] = 1; + if (result == firstFreeLocal) { + for (int i = firstFreeLocal + 2; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + } + else { + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getNewWordLocal() + { + short result = firstFreeLocal; + locals[result] = 1; + for (int i = firstFreeLocal + 1; i < MAX_LOCALS; i++) { + if (locals[i] == 0) { + firstFreeLocal = (short) i; + if (localsMax < firstFreeLocal) + localsMax = firstFreeLocal; + return result; + } + } + throw Context.reportRuntimeError("Program too complex " + + "(out of locals)"); + } + + private short getConsecutiveSlots(int count, boolean isConst) { + if (isConst) + count++; + short result = firstFreeLocal; + while (true) { + if (result >= (MAX_LOCALS - 1)) + break; + int i; + for (i = 0; i < count; i++) + if (locals[result + i] != 0) + break; + if (i >= count) + break; + result++; + } + return result; + } + + // This is a valid call only for a local that is allocated by default. + private void incReferenceWordLocal(short local) + { + locals[local]++; + } + + // This is a valid call only for a local that is allocated by default. + private void decReferenceWordLocal(short local) + { + locals[local]--; + } + + private void releaseWordLocal(short local) + { + if (local < firstFreeLocal) + firstFreeLocal = local; + locals[local] = 0; + } + + + static final int GENERATOR_TERMINATE = -1; + static final int GENERATOR_START = 0; + static final int GENERATOR_YIELD_START = 1; + + ClassFileWriter cfw; + Codegen codegen; + CompilerEnvirons compilerEnv; + ScriptOrFnNode scriptOrFn; + public int scriptOrFnIndex; + private int savedCodeOffset; + + private OptFunctionNode fnCurrent; + private boolean isTopLevel; + + private static final int MAX_LOCALS = 256; + private int[] locals; + private short firstFreeLocal; + private short localsMax; + + private int itsLineNumber; + + private boolean hasVarsInRegs; + private short[] varRegisters; + private boolean inDirectCallFunction; + private boolean itsForcedObjectParameters; + private int enterAreaStartLabel; + private int epilogueLabel; + + // special known locals. If you add a new local here, be sure + // to initialize it to -1 in initBodyGeneration + private short variableObjectLocal; + private short popvLocal; + private short contextLocal; + private short argsLocal; + private short operationLocal; + private short thisObjLocal; + private short funObjLocal; + private short itsZeroArgArray; + private short itsOneArgArray; + private short scriptRegexpLocal; + private short generatorStateLocal; + + private boolean isGenerator; + private int generatorSwitch; + private int maxLocals = 0; + private int maxStack = 0; + + private Hashtable finallys; + + class FinallyReturnPoint { + public ArrayList jsrPoints = new ArrayList(); + public int tableLabel = 0; + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java new file mode 100644 index 0000000..607e649 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/DataFlowBitSet.java @@ -0,0 +1,134 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * 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.optimizer; + +class DataFlowBitSet { + + private int itsBits[]; + private int itsSize; + + DataFlowBitSet(int size) + { + itsSize = size; + itsBits = new int[(size + 31) >> 5]; + } + + void set(int n) + { + if (!(0 <= n && n < itsSize)) badIndex(n); + itsBits[n >> 5] |= 1 << (n & 31); + } + + boolean test(int n) + { + if (!(0 <= n && n < itsSize)) badIndex(n); + return ((itsBits[n >> 5] & (1 << (n & 31))) != 0); + } + + void not() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = ~itsBits[i]; + } + + void clear(int n) + { + if (!(0 <= n && n < itsSize)) badIndex(n); + itsBits[n >> 5] &= ~(1 << (n & 31)); + } + + void clear() + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] = 0; + } + + void or(DataFlowBitSet b) + { + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) + itsBits[i] |= b.itsBits[i]; + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append("DataFlowBitSet, size = "); + sb.append(itsSize); + sb.append('\n'); + int bitsLength = itsBits.length; + for (int i = 0; i < bitsLength; i++) { + sb.append(Integer.toHexString(itsBits[i])); + sb.append(' '); + } + return sb.toString(); + } + + boolean df(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] | gen.itsBits[i]) & notKill.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } + + boolean df2(DataFlowBitSet in, DataFlowBitSet gen, DataFlowBitSet notKill) + { + int bitsLength = itsBits.length; + boolean changed = false; + for (int i = 0; i < bitsLength; i++) { + int oldBits = itsBits[i]; + itsBits[i] = (in.itsBits[i] & notKill.itsBits[i]) | gen.itsBits[i]; + changed |= (oldBits != itsBits[i]); + } + return changed; + } + + private void badIndex(int n) + { + throw new RuntimeException("DataFlowBitSet bad index " + n); + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java new file mode 100644 index 0000000..e043165 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptFunctionNode.java @@ -0,0 +1,149 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Bob Jervis + * Roger Lawrence + * + * 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.optimizer; + +import org.mozilla.javascript.*; + +final class OptFunctionNode +{ + OptFunctionNode(FunctionNode fnode) + { + this.fnode = fnode; + fnode.setCompilerData(this); + } + + static OptFunctionNode get(ScriptOrFnNode scriptOrFn, int i) + { + FunctionNode fnode = scriptOrFn.getFunctionNode(i); + return (OptFunctionNode)fnode.getCompilerData(); + } + + static OptFunctionNode get(ScriptOrFnNode scriptOrFn) + { + return (OptFunctionNode)scriptOrFn.getCompilerData(); + } + + boolean isTargetOfDirectCall() + { + return directTargetIndex >= 0; + } + + int getDirectTargetIndex() + { + return directTargetIndex; + } + + void setDirectTargetIndex(int directTargetIndex) + { + // One time action + if (directTargetIndex < 0 || this.directTargetIndex >= 0) + Kit.codeBug(); + this.directTargetIndex = directTargetIndex; + } + + void setParameterNumberContext(boolean b) + { + itsParameterNumberContext = b; + } + + boolean getParameterNumberContext() + { + return itsParameterNumberContext; + } + + int getVarCount() + { + return fnode.getParamAndVarCount(); + } + + boolean isParameter(int varIndex) + { + return varIndex < fnode.getParamCount(); + } + + boolean isNumberVar(int varIndex) + { + varIndex -= fnode.getParamCount(); + if (varIndex >= 0 && numberVarFlags != null) { + return numberVarFlags[varIndex]; + } + return false; + } + + void setIsNumberVar(int varIndex) + { + varIndex -= fnode.getParamCount(); + // Can only be used with non-parameters + if (varIndex < 0) Kit.codeBug(); + if (numberVarFlags == null) { + int size = fnode.getParamAndVarCount() - fnode.getParamCount(); + numberVarFlags = new boolean[size]; + } + numberVarFlags[varIndex] = true; + } + + int getVarIndex(Node n) + { + int index = n.getIntProp(Node.VARIABLE_PROP, -1); + if (index == -1) { + Node node; + int type = n.getType(); + if (type == Token.GETVAR) { + node = n; + } else if (type == Token.SETVAR || + type == Token.SETCONSTVAR) { + node = n.getFirstChild(); + } else { + throw Kit.codeBug(); + } + index = fnode.getIndexForNameNode(node); + if (index < 0) throw Kit.codeBug(); + n.putIntProp(Node.VARIABLE_PROP, index); + } + return index; + } + + FunctionNode fnode; + private boolean[] numberVarFlags; + private int directTargetIndex = -1; + private boolean itsParameterNumberContext; + boolean itsContainsCalls0; + boolean itsContainsCalls1; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java new file mode 100644 index 0000000..ba8ca03 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptRuntime.java @@ -0,0 +1,311 @@ +/* ***** 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): + * Norris Boyd + * Roger Lawrence + * Hannes Wallnoefer + * + * 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.optimizer; + +import org.mozilla.javascript.*; + +public final class OptRuntime extends ScriptRuntime +{ + + public static final Double zeroObj = new Double(0.0); + public static final Double oneObj = new Double(1.0); + public static final Double minusOneObj = new Double(-1.0); + + /** + * Implement ....() call shrinking optimizer code. + */ + public static Object call0(Callable fun, Scriptable thisObj, + Context cx, Scriptable scope) + { + return fun.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); + } + + /** + * Implement ....(arg) call shrinking optimizer code. + */ + public static Object call1(Callable fun, Scriptable thisObj, Object arg0, + Context cx, Scriptable scope) + { + return fun.call(cx, scope, thisObj, new Object[] { arg0 } ); + } + + /** + * Implement ....(arg0, arg1) call shrinking optimizer code. + */ + public static Object call2(Callable fun, Scriptable thisObj, + Object arg0, Object arg1, + Context cx, Scriptable scope) + { + return fun.call(cx, scope, thisObj, new Object[] { arg0, arg1 }); + } + + /** + * Implement ....(arg0, arg1, ...) call shrinking optimizer code. + */ + public static Object callN(Callable fun, Scriptable thisObj, + Object[] args, + Context cx, Scriptable scope) + { + return fun.call(cx, scope, thisObj, args); + } + + /** + * Implement name(args) call shrinking optimizer code. + */ + public static Object callName(Object[] args, String name, + Context cx, Scriptable scope) + { + Callable f = getNameFunctionAndThis(name, cx, scope); + Scriptable thisObj = lastStoredScriptable(cx); + return f.call(cx, scope, thisObj, args); + } + + /** + * Implement name() call shrinking optimizer code. + */ + public static Object callName0(String name, + Context cx, Scriptable scope) + { + Callable f = getNameFunctionAndThis(name, cx, scope); + Scriptable thisObj = lastStoredScriptable(cx); + return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); + } + + /** + * Implement x.property() call shrinking optimizer code. + */ + public static Object callProp0(Object value, String property, + Context cx, Scriptable scope) + { + Callable f = getPropFunctionAndThis(value, property, cx); + Scriptable thisObj = lastStoredScriptable(cx); + return f.call(cx, scope, thisObj, ScriptRuntime.emptyArgs); + } + + public static Object add(Object val1, double val2) + { + if (val1 instanceof Scriptable) + val1 = ((Scriptable) val1).getDefaultValue(null); + if (!(val1 instanceof String)) + return wrapDouble(toNumber(val1) + val2); + return ((String)val1).concat(toString(val2)); + } + + public static Object add(double val1, Object val2) + { + if (val2 instanceof Scriptable) + val2 = ((Scriptable) val2).getDefaultValue(null); + if (!(val2 instanceof String)) + return wrapDouble(toNumber(val2) + val1); + return toString(val1).concat((String)val2); + } + + public static Object elemIncrDecr(Object obj, double index, + Context cx, int incrDecrMask) + { + return ScriptRuntime.elemIncrDecr(obj, new Double(index), cx, + incrDecrMask); + } + + public static Object[] padStart(Object[] currentArgs, int count) { + Object[] result = new Object[currentArgs.length + count]; + System.arraycopy(currentArgs, 0, result, count, currentArgs.length); + return result; + } + + public static void initFunction(NativeFunction fn, int functionType, + Scriptable scope, Context cx) + { + ScriptRuntime.initFunction(cx, scope, fn, functionType, false); + } + + public static Object callSpecial(Context cx, Callable fun, + Scriptable thisObj, Object[] args, + Scriptable scope, + Scriptable callerThis, int callType, + String fileName, int lineNumber) + { + return ScriptRuntime.callSpecial(cx, fun, thisObj, args, scope, + callerThis, callType, + fileName, lineNumber); + } + + public static Object newObjectSpecial(Context cx, Object fun, + Object[] args, Scriptable scope, + Scriptable callerThis, int callType) + { + return ScriptRuntime.newSpecial(cx, fun, args, scope, callType); + } + + public static Double wrapDouble(double num) + { + if (num == 0.0) { + if (1 / num > 0) { + // +0.0 + return zeroObj; + } + } else if (num == 1.0) { + return oneObj; + } else if (num == -1.0) { + return minusOneObj; + } else if (num != num) { + return NaNobj; + } + return new Double(num); + } + + static String encodeIntArray(int[] array) + { + // XXX: this extremely inefficient for small integers + if (array == null) { return null; } + int n = array.length; + char[] buffer = new char[1 + n * 2]; + buffer[0] = 1; + for (int i = 0; i != n; ++i) { + int value = array[i]; + int shift = 1 + i * 2; + buffer[shift] = (char)(value >>> 16); + buffer[shift + 1] = (char)value; + } + return new String(buffer); + } + + private static int[] decodeIntArray(String str, int arraySize) + { + // XXX: this extremely inefficient for small integers + if (arraySize == 0) { + if (str != null) throw new IllegalArgumentException(); + return null; + } + if (str.length() != 1 + arraySize * 2 && str.charAt(0) != 1) { + throw new IllegalArgumentException(); + } + int[] array = new int[arraySize]; + for (int i = 0; i != arraySize; ++i) { + int shift = 1 + i * 2; + array[i] = (str.charAt(shift) << 16) | str.charAt(shift + 1); + } + return array; + } + + public static Scriptable newArrayLiteral(Object[] objects, + String encodedInts, + int skipCount, + Context cx, + Scriptable scope) + { + int[] skipIndexces = decodeIntArray(encodedInts, skipCount); + return newArrayLiteral(objects, skipIndexces, cx, scope); + } + + public static void main(final Script script, final String[] args) + { + Context.call(new ContextAction() { + public Object run(Context cx) + { + ScriptableObject global = getGlobal(cx); + + // get the command line arguments and define "arguments" + // array in the top-level object + Object[] argsCopy = new Object[args.length]; + System.arraycopy(args, 0, argsCopy, 0, args.length); + Scriptable argsObj = cx.newArray(global, argsCopy); + global.defineProperty("arguments", argsObj, + ScriptableObject.DONTENUM); + script.exec(cx, global); + return null; + } + }); + } + + public static void throwStopIteration(Object obj) { + throw new JavaScriptException( + NativeIterator.getStopIterationObject((Scriptable)obj), "", 0); + } + + public static Scriptable createNativeGenerator(NativeFunction funObj, + Scriptable scope, + Scriptable thisObj, + int maxLocals, + int maxStack) + { + return new NativeGenerator(scope, funObj, + new GeneratorState(thisObj, maxLocals, maxStack)); + } + + public static Object[] getGeneratorStackState(Object obj) { + GeneratorState rgs = (GeneratorState) obj; + if (rgs.stackState == null) + rgs.stackState = new Object[rgs.maxStack]; + return rgs.stackState; + } + + public static Object[] getGeneratorLocalsState(Object obj) { + GeneratorState rgs = (GeneratorState) obj; + if (rgs.localsState == null) + rgs.localsState = new Object[rgs.maxLocals]; + return rgs.localsState; + } + + public static class GeneratorState { + static final String CLASS_NAME = + "org/mozilla/javascript/optimizer/OptRuntime$GeneratorState"; + + public int resumptionPoint; + static final String resumptionPoint_NAME = "resumptionPoint"; + static final String resumptionPoint_TYPE = "I"; + + public Scriptable thisObj; + static final String thisObj_NAME = "thisObj"; + static final String thisObj_TYPE = + "Lorg/mozilla/javascript/Scriptable;"; + + Object[] stackState; + Object[] localsState; + int maxLocals; + int maxStack; + + GeneratorState(Scriptable thisObj, int maxLocals, int maxStack) { + this.thisObj = thisObj; + this.maxLocals = maxLocals; + this.maxStack = maxStack; + } + } +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java new file mode 100644 index 0000000..7cf679f --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/OptTransformer.java @@ -0,0 +1,133 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * 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.optimizer; + +import org.mozilla.javascript.*; +import java.util.Hashtable; + +/** + * This class performs node transforms to prepare for optimization. + * + * @see NodeTransformer + * @author Norris Boyd + */ + +class OptTransformer extends NodeTransformer { + + OptTransformer(Hashtable possibleDirectCalls, ObjArray directCallTargets) + { + this.possibleDirectCalls = possibleDirectCalls; + this.directCallTargets = directCallTargets; + } + + protected void visitNew(Node node, ScriptOrFnNode tree) { + detectDirectCall(node, tree); + super.visitNew(node, tree); + } + + protected void visitCall(Node node, ScriptOrFnNode tree) { + detectDirectCall(node, tree); + super.visitCall(node, tree); + } + + private void detectDirectCall(Node node, ScriptOrFnNode tree) + { + if (tree.getType() == Token.FUNCTION) { + Node left = node.getFirstChild(); + + // count the arguments + int argCount = 0; + Node arg = left.getNext(); + while (arg != null) { + arg = arg.getNext(); + argCount++; + } + + if (argCount == 0) { + OptFunctionNode.get(tree).itsContainsCalls0 = true; + } + + /* + * Optimize a call site by converting call("a", b, c) into : + * + * FunctionObjectFor"a" <-- instance variable init'd by constructor + * + * // this is a DIRECTCALL node + * fn = GetProp(tmp = GetBase("a"), "a"); + * if (fn == FunctionObjectFor"a") + * fn.call(tmp, b, c) + * else + * ScriptRuntime.Call(fn, tmp, b, c) + */ + if (possibleDirectCalls != null) { + String targetName = null; + if (left.getType() == Token.NAME) { + targetName = left.getString(); + } else if (left.getType() == Token.GETPROP) { + targetName = left.getFirstChild().getNext().getString(); + } else if (left.getType() == Token.GETPROPNOWARN) { + throw Kit.codeBug(); + } + if (targetName != null) { + OptFunctionNode ofn; + ofn = (OptFunctionNode)possibleDirectCalls.get(targetName); + if (ofn != null + && argCount == ofn.fnode.getParamCount() + && !ofn.fnode.requiresActivation()) + { + // Refuse to directCall any function with more + // than 32 parameters - prevent code explosion + // for wacky test cases + if (argCount <= 32) { + node.putProp(Node.DIRECTCALL_PROP, ofn); + if (!ofn.isTargetOfDirectCall()) { + int index = directCallTargets.size(); + directCallTargets.add(ofn); + ofn.setDirectTargetIndex(index); + } + } + } + } + } + } + } + + private Hashtable possibleDirectCalls; + private ObjArray directCallTargets; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java new file mode 100644 index 0000000..575c7e7 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/optimizer/Optimizer.java @@ -0,0 +1,510 @@ +/* ***** 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-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * + * 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.optimizer; + +import org.mozilla.javascript.*; + +class Optimizer +{ + + static final int NoType = 0; + static final int NumberType = 1; + static final int AnyType = 3; + + // It is assumed that (NumberType | AnyType) == AnyType + + void optimize(ScriptOrFnNode scriptOrFn) + { + // run on one function at a time for now + int functionCount = scriptOrFn.getFunctionCount(); + for (int i = 0; i != functionCount; ++i) { + OptFunctionNode f = OptFunctionNode.get(scriptOrFn, i); + optimizeFunction(f); + } + } + + private void optimizeFunction(OptFunctionNode theFunction) + { + if (theFunction.fnode.requiresActivation()) return; + + inDirectCallFunction = theFunction.isTargetOfDirectCall(); + this.theFunction = theFunction; + + ObjArray statementsArray = new ObjArray(); + buildStatementList_r(theFunction.fnode, statementsArray); + Node[] theStatementNodes = new Node[statementsArray.size()]; + statementsArray.toArray(theStatementNodes); + + Block.runFlowAnalyzes(theFunction, theStatementNodes); + + if (!theFunction.fnode.requiresActivation()) { + /* + * Now that we know which local vars are in fact always + * Numbers, we re-write the tree to take advantage of + * that. Any arithmetic or assignment op involving just + * Number typed vars is marked so that the codegen will + * generate non-object code. + */ + parameterUsedInNumberContext = false; + for (int i = 0; i < theStatementNodes.length; i++) { + rewriteForNumberVariables(theStatementNodes[i]); + } + theFunction.setParameterNumberContext(parameterUsedInNumberContext); + } + + } + + +/* + Each directCall parameter is passed as a pair of values - an object + and a double. The value passed depends on the type of value available at + the call site. If a double is available, the object in java/lang/Void.TYPE + is passed as the object value, and if an object value is available, then + 0.0 is passed as the double value. + + The receiving routine always tests the object value before proceeding. + If the parameter is being accessed in a 'Number Context' then the code + sequence is : + if ("parameter_objectValue" == java/lang/Void.TYPE) + ...fine..., use the parameter_doubleValue + else + toNumber(parameter_objectValue) + + and if the parameter is being referenced in an Object context, the code is + if ("parameter_objectValue" == java/lang/Void.TYPE) + new Double(parameter_doubleValue) + else + ...fine..., use the parameter_objectValue + + If the receiving code never uses the doubleValue, it is converted on + entry to a Double instead. +*/ + + +/* + We're referencing a node in a Number context (i.e. we'd prefer it + was a double value). If the node is a parameter in a directCall + function, mark it as being referenced in this context. +*/ + private void markDCPNumberContext(Node n) + { + if (inDirectCallFunction && n.getType() == Token.GETVAR) { + int varIndex = theFunction.getVarIndex(n); + if (theFunction.isParameter(varIndex)) { + parameterUsedInNumberContext = true; + } + } + } + + private boolean convertParameter(Node n) + { + if (inDirectCallFunction && n.getType() == Token.GETVAR) { + int varIndex = theFunction.getVarIndex(n); + if (theFunction.isParameter(varIndex)) { + n.removeProp(Node.ISNUMBER_PROP); + return true; + } + } + return false; + } + + private int rewriteForNumberVariables(Node n) + { + switch (n.getType()) { + case Token.EXPR_VOID : { + Node child = n.getFirstChild(); + int type = rewriteForNumberVariables(child); + if (type == NumberType) + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NoType; + } + case Token.NUMBER : + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + + case Token.GETVAR : + { + int varIndex = theFunction.getVarIndex(n); + if (inDirectCallFunction + && theFunction.isParameter(varIndex)) + { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + else if (theFunction.isNumberVar(varIndex)) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + return NoType; + } + + case Token.INC : + case Token.DEC : { + Node child = n.getFirstChild(); + // "child" will be GETVAR or GETPROP or GETELEM + if (child.getType() == Token.GETVAR) { + if (rewriteForNumberVariables(child) == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + markDCPNumberContext(child); + return NumberType; + } + return NoType; + } + else if (child.getType() == Token.GETELEM) { + return rewriteForNumberVariables(child); + } + return NoType; + } + case Token.SETVAR : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNext(); + int rType = rewriteForNumberVariables(rChild); + int varIndex = theFunction.getVarIndex(n); + if (inDirectCallFunction + && theFunction.isParameter(varIndex)) + { + if (rType == NumberType) { + if (!convertParameter(rChild)) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + markDCPNumberContext(rChild); + return NoType; + } + else + return rType; + } + else if (theFunction.isNumberVar(varIndex)) { + if (rType != NumberType) { + n.removeChild(rChild); + n.addChildToBack( + new Node(Token.TO_DOUBLE, rChild)); + } + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + markDCPNumberContext(rChild); + return NumberType; + } + else { + if (rType == NumberType) { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + n.addChildToBack( + new Node(Token.TO_OBJECT, rChild)); + } + } + return NoType; + } + } + case Token.LE : + case Token.LT : + case Token.GE : + case Token.GT : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNext(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return NoType; + } else if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT); + } + } + else if (convertParameter(rChild)) { + if (lType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT); + } + } + else { + if (lType == NumberType) { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + } + else { + n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT); + } + } + else { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT); + } + } + } + // we actually build a boolean value + return NoType; + } + + case Token.ADD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNext(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + + + if (convertParameter(lChild)) { + if (convertParameter(rChild)) { + return NoType; + } + else { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT); + } + } + } + else { + if (convertParameter(rChild)) { + if (lType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT); + } + } + else { + if (lType == NumberType) { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + else { + n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT); + } + } + else { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, + Node.RIGHT); + } + } + } + } + return NoType; + } + + case Token.BITXOR : + case Token.BITOR : + case Token.BITAND : + case Token.RSH : + case Token.LSH : + case Token.SUB : + case Token.MUL : + case Token.DIV : + case Token.MOD : { + Node lChild = n.getFirstChild(); + Node rChild = lChild.getNext(); + int lType = rewriteForNumberVariables(lChild); + int rType = rewriteForNumberVariables(rChild); + markDCPNumberContext(lChild); + markDCPNumberContext(rChild); + if (lType == NumberType) { + if (rType == NumberType) { + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + else { + if (!convertParameter(rChild)) { + n.removeChild(rChild); + n.addChildToBack( + new Node(Token.TO_DOUBLE, rChild)); + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + } + return NumberType; + } + } + else { + if (rType == NumberType) { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + n.addChildToFront( + new Node(Token.TO_DOUBLE, lChild)); + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + } + return NumberType; + } + else { + if (!convertParameter(lChild)) { + n.removeChild(lChild); + n.addChildToFront( + new Node(Token.TO_DOUBLE, lChild)); + } + if (!convertParameter(rChild)) { + n.removeChild(rChild); + n.addChildToBack( + new Node(Token.TO_DOUBLE, rChild)); + } + n.putIntProp(Node.ISNUMBER_PROP, Node.BOTH); + return NumberType; + } + } + } + case Token.SETELEM : + case Token.SETELEM_OP : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNext(); + Node rValue = arrayIndex.getNext(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + n.addChildToFront( + new Node(Token.TO_OBJECT, arrayBase)); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == NumberType) { + // setting the ISNUMBER_PROP signals the codegen + // to use the OptRuntime.setObjectIndex that takes + // a double index + n.putIntProp(Node.ISNUMBER_PROP, Node.LEFT); + markDCPNumberContext(arrayIndex); + } + int rValueType = rewriteForNumberVariables(rValue); + if (rValueType == NumberType) { + if (!convertParameter(rValue)) { + n.removeChild(rValue); + n.addChildToBack( + new Node(Token.TO_OBJECT, rValue)); + } + } + return NoType; + } + case Token.GETELEM : { + Node arrayBase = n.getFirstChild(); + Node arrayIndex = arrayBase.getNext(); + int baseType = rewriteForNumberVariables(arrayBase); + if (baseType == NumberType) {// can never happen ??? + if (!convertParameter(arrayBase)) { + n.removeChild(arrayBase); + n.addChildToFront( + new Node(Token.TO_OBJECT, arrayBase)); + } + } + int indexType = rewriteForNumberVariables(arrayIndex); + if (indexType == NumberType) { + if (!convertParameter(arrayIndex)) { + // setting the ISNUMBER_PROP signals the codegen + // to use the OptRuntime.getObjectIndex that takes + // a double index + n.putIntProp(Node.ISNUMBER_PROP, Node.RIGHT); + } + } + return NoType; + } + case Token.CALL : + { + Node child = n.getFirstChild(); // the function node + if (child.getType() == Token.GETELEM) { + // Optimization of x[0]() is not supported + // so bypass GETELEM optimization that + // rewriteForNumberVariables would trigger + rewriteAsObjectChildren(child, child.getFirstChild()); + } else { + rewriteForNumberVariables(child); + } + child = child.getNext(); // the first arg + + OptFunctionNode target + = (OptFunctionNode)n.getProp(Node.DIRECTCALL_PROP); + if (target != null) { +/* + we leave each child as a Number if it can be. The codegen will + handle moving the pairs of parameters. +*/ + while (child != null) { + int type = rewriteForNumberVariables(child); + if (type == NumberType) { + markDCPNumberContext(child); + } + child = child.getNext(); + } + } else { + rewriteAsObjectChildren(n, child); + } + return NoType; + } + default : { + rewriteAsObjectChildren(n, n.getFirstChild()); + return NoType; + } + } + } + + private void rewriteAsObjectChildren(Node n, Node child) + { + // Force optimized children to be objects + while (child != null) { + Node nextChild = child.getNext(); + int type = rewriteForNumberVariables(child); + if (type == NumberType) { + if (!convertParameter(child)) { + n.removeChild(child); + Node nuChild = new Node(Token.TO_OBJECT, child); + if (nextChild == null) + n.addChildToBack(nuChild); + else + n.addChildBefore(nuChild, nextChild); + } + } + child = nextChild; + } + } + + private static void buildStatementList_r(Node node, ObjArray statements) + { + int type = node.getType(); + if (type == Token.BLOCK + || type == Token.LOCAL_BLOCK + || type == Token.LOOP + || type == Token.FUNCTION) + { + Node child = node.getFirstChild(); + while (child != null) { + buildStatementList_r(child, statements); + child = child.getNext(); + } + } else { + statements.add(node); + } + } + + private boolean inDirectCallFunction; + OptFunctionNode theFunction; + private boolean parameterUsedInNumberContext; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExp.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExp.java new file mode 100644 index 0000000..a893841 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExp.java @@ -0,0 +1,2782 @@ +/* -*- 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, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Brendan Eich + * Matthias Radestock + * + * 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.regexp; + +import java.io.Serializable; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.IdFunctionObject; +import org.mozilla.javascript.IdScriptableObject; +import org.mozilla.javascript.Kit; +import org.mozilla.javascript.ScriptRuntime; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.ScriptableObject; +import org.mozilla.javascript.Undefined; + +/** + * This class implements the RegExp native object. + * + * Revision History: + * Implementation in C by Brendan Eich + * Initial port to Java by Norris Boyd from jsregexp.c version 1.36 + * Merged up to version 1.38, which included Unicode support. + * Merged bug fixes in version 1.39. + * Merged JSFUN13_BRANCH changes up to 1.32.2.13 + * + * @author Brendan Eich + * @author Norris Boyd + */ + + + +public class NativeRegExp extends IdScriptableObject implements Function +{ + static final long serialVersionUID = 4965263491464903264L; + + private static final Object REGEXP_TAG = new Object(); + + public static final int JSREG_GLOB = 0x1; // 'g' flag: global + public static final int JSREG_FOLD = 0x2; // 'i' flag: fold + public static final int JSREG_MULTILINE = 0x4; // 'm' flag: multiline + + //type of match to perform + public static final int TEST = 0; + public static final int MATCH = 1; + public static final int PREFIX = 2; + + private static final boolean debug = false; + + private static final byte REOP_EMPTY = 0; /* match rest of input against rest of r.e. */ + private static final byte REOP_ALT = 1; /* alternative subexpressions in kid and next */ + private static final byte REOP_BOL = 2; /* beginning of input (or line if multiline) */ + private static final byte REOP_EOL = 3; /* end of input (or line if multiline) */ + private static final byte REOP_WBDRY = 4; /* match "" at word boundary */ + private static final byte REOP_WNONBDRY = 5; /* match "" at word non-boundary */ + private static final byte REOP_QUANT = 6; /* quantified atom: atom{1,2} */ + private static final byte REOP_STAR = 7; /* zero or more occurrences of kid */ + private static final byte REOP_PLUS = 8; /* one or more occurrences of kid */ + private static final byte REOP_OPT = 9; /* optional subexpression in kid */ + private static final byte REOP_LPAREN = 10; /* left paren bytecode: kid is u.num'th sub-regexp */ + private static final byte REOP_RPAREN = 11; /* right paren bytecode */ + private static final byte REOP_DOT = 12; /* stands for any character */ +// private static final byte REOP_CCLASS = 13; /* character class: [a-f] */ + private static final byte REOP_DIGIT = 14; /* match a digit char: [0-9] */ + private static final byte REOP_NONDIGIT = 15; /* match a non-digit char: [^0-9] */ + private static final byte REOP_ALNUM = 16; /* match an alphanumeric char: [0-9a-z_A-Z] */ + private static final byte REOP_NONALNUM = 17; /* match a non-alphanumeric char: [^0-9a-z_A-Z] */ + private static final byte REOP_SPACE = 18; /* match a whitespace char */ + private static final byte REOP_NONSPACE = 19; /* match a non-whitespace char */ + private static final byte REOP_BACKREF = 20; /* back-reference (e.g., \1) to a parenthetical */ + private static final byte REOP_FLAT = 21; /* match a flat string */ + private static final byte REOP_FLAT1 = 22; /* match a single char */ + private static final byte REOP_JUMP = 23; /* for deoptimized closure loops */ +// private static final byte REOP_DOTSTAR = 24; /* optimize .* to use a single opcode */ +// private static final byte REOP_ANCHOR = 25; /* like .* but skips left context to unanchored r.e. */ +// private static final byte REOP_EOLONLY = 26; /* $ not preceded by any pattern */ +// private static final byte REOP_UCFLAT = 27; /* flat Unicode string; len immediate counts chars */ + private static final byte REOP_UCFLAT1 = 28; /* single Unicode char */ +// private static final byte REOP_UCCLASS = 29; /* Unicode character class, vector of chars to match */ +// private static final byte REOP_NUCCLASS = 30; /* negated Unicode character class */ +// private static final byte REOP_BACKREFi = 31; /* case-independent REOP_BACKREF */ + private static final byte REOP_FLATi = 32; /* case-independent REOP_FLAT */ + private static final byte REOP_FLAT1i = 33; /* case-independent REOP_FLAT1 */ +// private static final byte REOP_UCFLATi = 34; /* case-independent REOP_UCFLAT */ + private static final byte REOP_UCFLAT1i = 35; /* case-independent REOP_UCFLAT1 */ +// private static final byte REOP_ANCHOR1 = 36; /* first-char discriminating REOP_ANCHOR */ +// private static final byte REOP_NCCLASS = 37; /* negated 8-bit character class */ +// private static final byte REOP_DOTSTARMIN = 38; /* ungreedy version of REOP_DOTSTAR */ +// private static final byte REOP_LPARENNON = 39; /* non-capturing version of REOP_LPAREN */ +// private static final byte REOP_RPARENNON = 40; /* non-capturing version of REOP_RPAREN */ + private static final byte REOP_ASSERT = 41; /* zero width positive lookahead assertion */ + private static final byte REOP_ASSERT_NOT = 42; /* zero width negative lookahead assertion */ + private static final byte REOP_ASSERTTEST = 43; /* sentinel at end of assertion child */ + private static final byte REOP_ASSERTNOTTEST = 44; /* sentinel at end of !assertion child */ + private static final byte REOP_MINIMALSTAR = 45; /* non-greedy version of * */ + private static final byte REOP_MINIMALPLUS = 46; /* non-greedy version of + */ + private static final byte REOP_MINIMALOPT = 47; /* non-greedy version of ? */ + private static final byte REOP_MINIMALQUANT = 48; /* non-greedy version of {} */ + private static final byte REOP_ENDCHILD = 49; /* sentinel at end of quantifier child */ + private static final byte REOP_CLASS = 50; /* character class with index */ + private static final byte REOP_REPEAT = 51; /* directs execution of greedy quantifier */ + private static final byte REOP_MINIMALREPEAT = 52; /* directs execution of non-greedy quantifier */ + private static final byte REOP_END = 53; + + + + public static void init(Context cx, Scriptable scope, boolean sealed) + { + + NativeRegExp proto = new NativeRegExp(); + proto.re = (RECompiled)compileRE(cx, "", null, false); + proto.activatePrototypeMap(MAX_PROTOTYPE_ID); + proto.setParentScope(scope); + proto.setPrototype(getObjectPrototype(scope)); + + NativeRegExpCtor ctor = new NativeRegExpCtor(); + // Bug #324006: ECMA-262 15.10.6.1 says "The initial value of + // RegExp.prototype.constructor is the builtin RegExp constructor." + proto.put("constructor", proto, ctor); + + ScriptRuntime.setFunctionProtoAndParent(ctor, scope); + + ctor.setImmunePrototypeProperty(proto); + + if (sealed) { + proto.sealObject(); + ctor.sealObject(); + } + + defineProperty(scope, "RegExp", ctor, ScriptableObject.DONTENUM); + } + + NativeRegExp(Scriptable scope, Object regexpCompiled) + { + this.re = (RECompiled)regexpCompiled; + this.lastIndex = 0; + ScriptRuntime.setObjectProtoAndParent(this, scope); + } + + public String getClassName() + { + return "RegExp"; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + return execSub(cx, scope, args, MATCH); + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + return (Scriptable)execSub(cx, scope, args, MATCH); + } + + Scriptable compile(Context cx, Scriptable scope, Object[] args) + { + if (args.length > 0 && args[0] instanceof NativeRegExp) { + if (args.length > 1 && args[1] != Undefined.instance) { + // report error + throw ScriptRuntime.typeError0("msg.bad.regexp.compile"); + } + NativeRegExp thatObj = (NativeRegExp) args[0]; + this.re = thatObj.re; + this.lastIndex = thatObj.lastIndex; + return this; + } + String s = args.length == 0 ? "" : ScriptRuntime.toString(args[0]); + String global = args.length > 1 && args[1] != Undefined.instance + ? ScriptRuntime.toString(args[1]) + : null; + this.re = (RECompiled)compileRE(cx, s, global, false); + this.lastIndex = 0; + return this; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + buf.append('/'); + if (re.source.length != 0) { + buf.append(re.source); + } else { + // See bugzilla 226045 + buf.append("(?:)"); + } + buf.append('/'); + if ((re.flags & JSREG_GLOB) != 0) + buf.append('g'); + if ((re.flags & JSREG_FOLD) != 0) + buf.append('i'); + if ((re.flags & JSREG_MULTILINE) != 0) + buf.append('m'); + return buf.toString(); + } + + NativeRegExp() { } + + private static RegExpImpl getImpl(Context cx) + { + return (RegExpImpl) ScriptRuntime.getRegExpProxy(cx); + } + + private Object execSub(Context cx, Scriptable scopeObj, + Object[] args, int matchType) + { + RegExpImpl reImpl = getImpl(cx); + String str; + if (args.length == 0) { + str = reImpl.input; + if (str == null) { + reportError("msg.no.re.input.for", toString()); + } + } else { + str = ScriptRuntime.toString(args[0]); + } + double d = ((re.flags & JSREG_GLOB) != 0) ? lastIndex : 0; + + Object rval; + if (d < 0 || str.length() < d) { + lastIndex = 0; + rval = null; + } + else { + int indexp[] = { (int)d }; + rval = executeRegExp(cx, scopeObj, reImpl, str, indexp, matchType); + if ((re.flags & JSREG_GLOB) != 0) { + lastIndex = (rval == null || rval == Undefined.instance) + ? 0 : indexp[0]; + } + } + return rval; + } + + static Object compileRE(Context cx, String str, String global, boolean flat) + { + RECompiled regexp = new RECompiled(); + regexp.source = str.toCharArray(); + int length = str.length(); + + int flags = 0; + if (global != null) { + for (int i = 0; i < global.length(); i++) { + char c = global.charAt(i); + if (c == 'g') { + flags |= JSREG_GLOB; + } else if (c == 'i') { + flags |= JSREG_FOLD; + } else if (c == 'm') { + flags |= JSREG_MULTILINE; + } else { + reportError("msg.invalid.re.flag", String.valueOf(c)); + } + } + } + regexp.flags = flags; + + CompilerState state = new CompilerState(cx, regexp.source, length, flags); + if (flat && length > 0) { +if (debug) { +System.out.println("flat = \"" + str + "\""); +} + state.result = new RENode(REOP_FLAT); + state.result.chr = state.cpbegin[0]; + state.result.length = length; + state.result.flatIndex = 0; + state.progLength += 5; + } + else + if (!parseDisjunction(state)) + return null; + + regexp.program = new byte[state.progLength + 1]; + if (state.classCount != 0) { + regexp.classList = new RECharSet[state.classCount]; + regexp.classCount = state.classCount; + } + int endPC = emitREBytecode(state, regexp, 0, state.result); + regexp.program[endPC++] = REOP_END; + +if (debug) { +System.out.println("Prog. length = " + endPC); +for (int i = 0; i < endPC; i++) { + System.out.print(regexp.program[i]); + if (i < (endPC - 1)) System.out.print(", "); +} +System.out.println(); +} + regexp.parenCount = state.parenCount; + + // If re starts with literal, init anchorCh accordingly + switch (regexp.program[0]) { + case REOP_UCFLAT1: + case REOP_UCFLAT1i: + regexp.anchorCh = (char)getIndex(regexp.program, 1); + break; + case REOP_FLAT1: + case REOP_FLAT1i: + regexp.anchorCh = (char)(regexp.program[1] & 0xFF); + break; + case REOP_FLAT: + case REOP_FLATi: + int k = getIndex(regexp.program, 1); + regexp.anchorCh = regexp.source[k]; + break; + } + +if (debug) { +if (regexp.anchorCh >= 0) { + System.out.println("Anchor ch = '" + (char)regexp.anchorCh + "'"); +} +} + return regexp; + } + + static boolean isDigit(char c) + { + return '0' <= c && c <= '9'; + } + + private static boolean isWord(char c) + { + return Character.isLetter(c) || isDigit(c) || c == '_'; + } + + private static boolean isLineTerm(char c) + { + return ScriptRuntime.isJSLineTerminator(c); + } + + private static boolean isREWhiteSpace(int c) + { + return (c == '\u0020' || c == '\u0009' + || c == '\n' || c == '\r' + || c == 0x2028 || c == 0x2029 + || c == '\u000C' || c == '\u000B' + || c == '\u00A0' + || Character.getType((char)c) == Character.SPACE_SEPARATOR); + } + + /* + * + * 1. If IgnoreCase is false, return ch. + * 2. Let u be ch converted to upper case as if by calling + * String.prototype.toUpperCase on the one-character string ch. + * 3. If u does not consist of a single character, return ch. + * 4. Let cu be u's character. + * 5. If ch's code point value is greater than or equal to decimal 128 and cu's + * code point value is less than decimal 128, then return ch. + * 6. Return cu. + */ + private static char upcase(char ch) + { + if (ch < 128) { + if ('a' <= ch && ch <= 'z') { + return (char)(ch + ('A' - 'a')); + } + return ch; + } + char cu = Character.toUpperCase(ch); + if ((ch >= 128) && (cu < 128)) return ch; + return cu; + } + + private static char downcase(char ch) + { + if (ch < 128) { + if ('A' <= ch && ch <= 'Z') { + return (char)(ch + ('a' - 'A')); + } + return ch; + } + char cl = Character.toLowerCase(ch); + if ((ch >= 128) && (cl < 128)) return ch; + return cl; + } + +/* + * Validates and converts hex ascii value. + */ + private static int toASCIIHexDigit(int c) + { + if (c < '0') + return -1; + if (c <= '9') { + return c - '0'; + } + c |= 0x20; + if ('a' <= c && c <= 'f') { + return c - 'a' + 10; + } + return -1; + } + +/* + * Top-down regular expression grammar, based closely on Perl4. + * + * regexp: altern A regular expression is one or more + * altern '|' regexp alternatives separated by vertical bar. + */ + private static boolean parseDisjunction(CompilerState state) + { + if (!parseAlternative(state)) + return false; + char[] source = state.cpbegin; + int index = state.cp; + if (index != source.length && source[index] == '|') { + RENode altResult; + ++state.cp; + altResult = new RENode(REOP_ALT); + altResult.kid = state.result; + if (!parseDisjunction(state)) + return false; + altResult.kid2 = state.result; + state.result = altResult; + /* ALT, <next>, ..., JUMP, <end> ... JUMP <end> */ + state.progLength += 9; + } + return true; + } + +/* + * altern: item An alternative is one or more items, + * item altern concatenated together. + */ + private static boolean parseAlternative(CompilerState state) + { + RENode headTerm = null; + RENode tailTerm = null; + char[] source = state.cpbegin; + while (true) { + if (state.cp == state.cpend || source[state.cp] == '|' + || (state.parenNesting != 0 && source[state.cp] == ')')) + { + if (headTerm == null) { + state.result = new RENode(REOP_EMPTY); + } + else + state.result = headTerm; + return true; + } + if (!parseTerm(state)) + return false; + if (headTerm == null) + headTerm = state.result; + else { + if (tailTerm == null) { + headTerm.next = state.result; + tailTerm = state.result; + while (tailTerm.next != null) tailTerm = tailTerm.next; + } + else { + tailTerm.next = state.result; + tailTerm = tailTerm.next; + while (tailTerm.next != null) tailTerm = tailTerm.next; + } + } + } + } + + /* calculate the total size of the bitmap required for a class expression */ + private static boolean + calculateBitmapSize(CompilerState state, RENode target, char[] src, + int index, int end) + { + char rangeStart = 0; + char c; + int n; + int nDigits; + int i; + int max = 0; + boolean inRange = false; + + target.bmsize = 0; + + if (index == end) + return true; + + if (src[index] == '^') + ++index; + + while (index != end) { + int localMax = 0; + nDigits = 2; + switch (src[index]) { + case '\\': + ++index; + c = src[index++]; + switch (c) { + case 'b': + localMax = 0x8; + break; + case 'f': + localMax = 0xC; + break; + case 'n': + localMax = 0xA; + break; + case 'r': + localMax = 0xD; + break; + case 't': + localMax = 0x9; + break; + case 'v': + localMax = 0xB; + break; + case 'c': + if (((index + 1) < end) && Character.isLetter(src[index + 1])) + localMax = (char)(src[index++] & 0x1F); + else + localMax = '\\'; + break; + case 'u': + nDigits += 2; + // fall thru... + case 'x': + n = 0; + for (i = 0; (i < nDigits) && (index < end); i++) { + c = src[index++]; + n = Kit.xDigitToInt(c, n); + if (n < 0) { + // Back off to accepting the original + // '\' as a literal + index -= (i + 1); + n = '\\'; + break; + } + } + localMax = n; + break; + case 'd': + if (inRange) { + reportError("msg.bad.range", ""); + return false; + } + localMax = '9'; + break; + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + if (inRange) { + reportError("msg.bad.range", ""); + return false; + } + target.bmsize = 65535; + return true; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = (c - '0'); + c = src[index]; + if ('0' <= c && c <= '7') { + index++; + n = 8 * n + (c - '0'); + c = src[index]; + if ('0' <= c && c <= '7') { + index++; + i = 8 * n + (c - '0'); + if (i <= 0377) + n = i; + else + index--; + } + } + localMax = n; + break; + + default: + localMax = c; + break; + } + break; + default: + localMax = src[index++]; + break; + } + if (inRange) { + if (rangeStart > localMax) { + reportError("msg.bad.range", ""); + return false; + } + inRange = false; + } + else { + if (index < (end - 1)) { + if (src[index] == '-') { + ++index; + inRange = true; + rangeStart = (char)localMax; + continue; + } + } + } + if ((state.flags & JSREG_FOLD) != 0){ + char cu = upcase((char)localMax); + char cd = downcase((char)localMax); + localMax = (cu >= cd) ? cu : cd; + } + if (localMax > max) + max = localMax; + } + target.bmsize = max; + return true; + } + + /* + * item: assertion An item is either an assertion or + * quantatom a quantified atom. + * + * assertion: '^' Assertions match beginning of string + * (or line if the class static property + * RegExp.multiline is true). + * '$' End of string (or line if the class + * static property RegExp.multiline is + * true). + * '\b' Word boundary (between \w and \W). + * '\B' Word non-boundary. + * + * quantatom: atom An unquantified atom. + * quantatom '{' n ',' m '}' + * Atom must occur between n and m times. + * quantatom '{' n ',' '}' Atom must occur at least n times. + * quantatom '{' n '}' Atom must occur exactly n times. + * quantatom '*' Zero or more times (same as {0,}). + * quantatom '+' One or more times (same as {1,}). + * quantatom '?' Zero or one time (same as {0,1}). + * + * any of which can be optionally followed by '?' for ungreedy + * + * atom: '(' regexp ')' A parenthesized regexp (what matched + * can be addressed using a backreference, + * see '\' n below). + * '.' Matches any char except '\n'. + * '[' classlist ']' A character class. + * '[' '^' classlist ']' A negated character class. + * '\f' Form Feed. + * '\n' Newline (Line Feed). + * '\r' Carriage Return. + * '\t' Horizontal Tab. + * '\v' Vertical Tab. + * '\d' A digit (same as [0-9]). + * '\D' A non-digit. + * '\w' A word character, [0-9a-z_A-Z]. + * '\W' A non-word character. + * '\s' A whitespace character, [ \b\f\n\r\t\v]. + * '\S' A non-whitespace character. + * '\' n A backreference to the nth (n decimal + * and positive) parenthesized expression. + * '\' octal An octal escape sequence (octal must be + * two or three digits long, unless it is + * 0 for the null character). + * '\x' hex A hex escape (hex must be two digits). + * '\c' ctrl A control character, ctrl is a letter. + * '\' literalatomchar Any character except one of the above + * that follow '\' in an atom. + * otheratomchar Any character not first among the other + * atom right-hand sides. + */ + + private static void doFlat(CompilerState state, char c) + { + state.result = new RENode(REOP_FLAT); + state.result.chr = c; + state.result.length = 1; + state.result.flatIndex = -1; + state.progLength += 3; + } + + private static int + getDecimalValue(char c, CompilerState state, int maxValue, + String overflowMessageId) + { + boolean overflow = false; + int start = state.cp; + char[] src = state.cpbegin; + int value = c - '0'; + for (; state.cp != state.cpend; ++state.cp) { + c = src[state.cp]; + if (!isDigit(c)) { + break; + } + if (!overflow) { + int digit = c - '0'; + if (value < (maxValue - digit) / 10) { + value = value * 10 + digit; + } else { + overflow = true; + value = maxValue; + } + } + } + if (overflow) { + reportError(overflowMessageId, + String.valueOf(src, start, state.cp - start)); + } + return value; + } + + private static boolean + parseTerm(CompilerState state) + { + char[] src = state.cpbegin; + char c = src[state.cp++]; + int nDigits = 2; + int parenBaseCount = state.parenCount; + int num, tmp; + RENode term; + int termStart; + + switch (c) { + /* assertions and atoms */ + case '^': + state.result = new RENode(REOP_BOL); + state.progLength++; + return true; + case '$': + state.result = new RENode(REOP_EOL); + state.progLength++; + return true; + case '\\': + if (state.cp < state.cpend) { + c = src[state.cp++]; + switch (c) { + /* assertion escapes */ + case 'b' : + state.result = new RENode(REOP_WBDRY); + state.progLength++; + return true; + case 'B': + state.result = new RENode(REOP_WNONBDRY); + state.progLength++; + return true; + /* Decimal escape */ + case '0': +/* + * Under 'strict' ECMA 3, we interpret \0 as NUL and don't accept octal. + * However, (XXX and since Rhino doesn't have a 'strict' mode) we'll just + * behave the old way for compatibility reasons. + * (see http://bugzilla.mozilla.org/show_bug.cgi?id=141078) + * + */ + reportWarning(state.cx, "msg.bad.backref", ""); + /* octal escape */ + num = 0; + while (state.cp < state.cpend) { + c = src[state.cp]; + if ((c >= '0') && (c <= '7')) { + state.cp++; + tmp = 8 * num + (c - '0'); + if (tmp > 0377) + break; + num = tmp; + } + else + break; + } + c = (char)(num); + doFlat(state, c); + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + termStart = state.cp - 1; + num = getDecimalValue(c, state, 0xFFFF, + "msg.overlarge.backref"); + if (num > state.parenCount) + reportWarning(state.cx, "msg.bad.backref", ""); + /* + * n > 9 or > count of parentheses, + * then treat as octal instead. + */ + if ((num > 9) && (num > state.parenCount)) { + state.cp = termStart; + num = 0; + while (state.cp < state.cpend) { + c = src[state.cp]; + if ((c >= '0') && (c <= '7')) { + state.cp++; + tmp = 8 * num + (c - '0'); + if (tmp > 0377) + break; + num = tmp; + } + else + break; + } + c = (char)(num); + doFlat(state, c); + break; + } + /* otherwise, it's a back-reference */ + state.result = new RENode(REOP_BACKREF); + state.result.parenIndex = num - 1; + state.progLength += 3; + break; + /* Control escape */ + case 'f': + c = 0xC; + doFlat(state, c); + break; + case 'n': + c = 0xA; + doFlat(state, c); + break; + case 'r': + c = 0xD; + doFlat(state, c); + break; + case 't': + c = 0x9; + doFlat(state, c); + break; + case 'v': + c = 0xB; + doFlat(state, c); + break; + /* Control letter */ + case 'c': + if (((state.cp + 1) < state.cpend) && + Character.isLetter(src[state.cp + 1])) + c = (char)(src[state.cp++] & 0x1F); + else { + /* back off to accepting the original '\' as a literal */ + --state.cp; + c = '\\'; + } + doFlat(state, c); + break; + /* UnicodeEscapeSequence */ + case 'u': + nDigits += 2; + // fall thru... + /* HexEscapeSequence */ + case 'x': + { + int n = 0; + int i; + for (i = 0; (i < nDigits) + && (state.cp < state.cpend); i++) { + c = src[state.cp++]; + n = Kit.xDigitToInt(c, n); + if (n < 0) { + // Back off to accepting the original + // 'u' or 'x' as a literal + state.cp -= (i + 2); + n = src[state.cp++]; + break; + } + } + c = (char)(n); + } + doFlat(state, c); + break; + /* Character class escapes */ + case 'd': + state.result = new RENode(REOP_DIGIT); + state.progLength++; + break; + case 'D': + state.result = new RENode(REOP_NONDIGIT); + state.progLength++; + break; + case 's': + state.result = new RENode(REOP_SPACE); + state.progLength++; + break; + case 'S': + state.result = new RENode(REOP_NONSPACE); + state.progLength++; + break; + case 'w': + state.result = new RENode(REOP_ALNUM); + state.progLength++; + break; + case 'W': + state.result = new RENode(REOP_NONALNUM); + state.progLength++; + break; + /* IdentityEscape */ + default: + state.result = new RENode(REOP_FLAT); + state.result.chr = c; + state.result.length = 1; + state.result.flatIndex = state.cp - 1; + state.progLength += 3; + break; + } + break; + } + else { + /* a trailing '\' is an error */ + reportError("msg.trail.backslash", ""); + return false; + } + case '(': { + RENode result = null; + termStart = state.cp; + if (state.cp + 1 < state.cpend && src[state.cp] == '?' + && ((c = src[state.cp + 1]) == '=' || c == '!' || c == ':')) + { + state.cp += 2; + if (c == '=') { + result = new RENode(REOP_ASSERT); + /* ASSERT, <next>, ... ASSERTTEST */ + state.progLength += 4; + } else if (c == '!') { + result = new RENode(REOP_ASSERT_NOT); + /* ASSERTNOT, <next>, ... ASSERTNOTTEST */ + state.progLength += 4; + } + } else { + result = new RENode(REOP_LPAREN); + /* LPAREN, <index>, ... RPAREN, <index> */ + state.progLength += 6; + result.parenIndex = state.parenCount++; + } + ++state.parenNesting; + if (!parseDisjunction(state)) + return false; + if (state.cp == state.cpend || src[state.cp] != ')') { + reportError("msg.unterm.paren", "in regular expression"/*APPJET*/); + return false; + } + ++state.cp; + --state.parenNesting; + if (result != null) { + result.kid = state.result; + state.result = result; + } + break; + } + case ')': + reportError("msg.re.unmatched.right.paren", ""); + return false; + case '[': + state.result = new RENode(REOP_CLASS); + termStart = state.cp; + state.result.startIndex = termStart; + while (true) { + if (state.cp == state.cpend) { + reportError("msg.unterm.class", ""); + return false; + } + if (src[state.cp] == '\\') + state.cp++; + else { + if (src[state.cp] == ']') { + state.result.kidlen = state.cp - termStart; + break; + } + } + state.cp++; + } + state.result.index = state.classCount++; + /* + * Call calculateBitmapSize now as we want any errors it finds + * to be reported during the parse phase, not at execution. + */ + if (!calculateBitmapSize(state, state.result, src, termStart, state.cp++)) + return false; + state.progLength += 3; /* CLASS, <index> */ + break; + + case '.': + state.result = new RENode(REOP_DOT); + state.progLength++; + break; + case '*': + case '+': + case '?': + reportError("msg.bad.quant", String.valueOf(src[state.cp - 1])); + return false; + default: + state.result = new RENode(REOP_FLAT); + state.result.chr = c; + state.result.length = 1; + state.result.flatIndex = state.cp - 1; + state.progLength += 3; + break; + } + + term = state.result; + if (state.cp == state.cpend) { + return true; + } + boolean hasQ = false; + switch (src[state.cp]) { + case '+': + state.result = new RENode(REOP_QUANT); + state.result.min = 1; + state.result.max = -1; + /* <PLUS>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */ + state.progLength += 8; + hasQ = true; + break; + case '*': + state.result = new RENode(REOP_QUANT); + state.result.min = 0; + state.result.max = -1; + /* <STAR>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */ + state.progLength += 8; + hasQ = true; + break; + case '?': + state.result = new RENode(REOP_QUANT); + state.result.min = 0; + state.result.max = 1; + /* <OPT>, <parencount>, <parenindex>, <next> ... <ENDCHILD> */ + state.progLength += 8; + hasQ = true; + break; + case '{': /* balance '}' */ + { + int min = 0; + int max = -1; + int leftCurl = state.cp; + + /* For Perl etc. compatibility, if quntifier does not match + * \{\d+(,\d*)?\} exactly back off from it + * being a quantifier, and chew it up as a literal + * atom next time instead. + */ + + c = src[++state.cp]; + if (isDigit(c)) { + ++state.cp; + min = getDecimalValue(c, state, 0xFFFF, + "msg.overlarge.min"); + c = src[state.cp]; + if (c == ',') { + c = src[++state.cp]; + if (isDigit(c)) { + ++state.cp; + max = getDecimalValue(c, state, 0xFFFF, + "msg.overlarge.max"); + c = src[state.cp]; + if (min > max) { + reportError("msg.max.lt.min", + String.valueOf(src[state.cp])); + return false; + } + } + } else { + max = min; + } + /* balance '{' */ + if (c == '}') { + state.result = new RENode(REOP_QUANT); + state.result.min = min; + state.result.max = max; + // QUANT, <min>, <max>, <parencount>, + // <parenindex>, <next> ... <ENDCHILD> + state.progLength += 12; + hasQ = true; + } + } + if (!hasQ) { + state.cp = leftCurl; + } + break; + } + } + if (!hasQ) + return true; + + ++state.cp; + state.result.kid = term; + state.result.parenIndex = parenBaseCount; + state.result.parenCount = state.parenCount - parenBaseCount; + if ((state.cp < state.cpend) && (src[state.cp] == '?')) { + ++state.cp; + state.result.greedy = false; + } + else + state.result.greedy = true; + return true; + } + + private static void resolveForwardJump(byte[] array, int from, int pc) + { + if (from > pc) throw Kit.codeBug(); + addIndex(array, from, pc - from); + } + + private static int getOffset(byte[] array, int pc) + { + return getIndex(array, pc); + } + + private static int addIndex(byte[] array, int pc, int index) + { + if (index < 0) throw Kit.codeBug(); + if (index > 0xFFFF) + throw Context.reportRuntimeError("Too complex regexp"); + array[pc] = (byte)(index >> 8); + array[pc + 1] = (byte)(index); + return pc + 2; + } + + private static int getIndex(byte[] array, int pc) + { + return ((array[pc] & 0xFF) << 8) | (array[pc + 1] & 0xFF); + } + + private static final int OFFSET_LEN = 2; + private static final int INDEX_LEN = 2; + + private static int + emitREBytecode(CompilerState state, RECompiled re, int pc, RENode t) + { + RENode nextAlt; + int nextAltFixup, nextTermFixup; + byte[] program = re.program; + + while (t != null) { + program[pc++] = t.op; + switch (t.op) { + case REOP_EMPTY: + --pc; + break; + case REOP_ALT: + nextAlt = t.kid2; + nextAltFixup = pc; /* address of next alternate */ + pc += OFFSET_LEN; + pc = emitREBytecode(state, re, pc, t.kid); + program[pc++] = REOP_JUMP; + nextTermFixup = pc; /* address of following term */ + pc += OFFSET_LEN; + resolveForwardJump(program, nextAltFixup, pc); + pc = emitREBytecode(state, re, pc, nextAlt); + + program[pc++] = REOP_JUMP; + nextAltFixup = pc; + pc += OFFSET_LEN; + + resolveForwardJump(program, nextTermFixup, pc); + resolveForwardJump(program, nextAltFixup, pc); + break; + case REOP_FLAT: + /* + * Consecutize FLAT's if possible. + */ + if (t.flatIndex != -1) { + while ((t.next != null) && (t.next.op == REOP_FLAT) + && ((t.flatIndex + t.length) + == t.next.flatIndex)) { + t.length += t.next.length; + t.next = t.next.next; + } + } + if ((t.flatIndex != -1) && (t.length > 1)) { + if ((state.flags & JSREG_FOLD) != 0) + program[pc - 1] = REOP_FLATi; + else + program[pc - 1] = REOP_FLAT; + pc = addIndex(program, pc, t.flatIndex); + pc = addIndex(program, pc, t.length); + } + else { + if (t.chr < 256) { + if ((state.flags & JSREG_FOLD) != 0) + program[pc - 1] = REOP_FLAT1i; + else + program[pc - 1] = REOP_FLAT1; + program[pc++] = (byte)(t.chr); + } + else { + if ((state.flags & JSREG_FOLD) != 0) + program[pc - 1] = REOP_UCFLAT1i; + else + program[pc - 1] = REOP_UCFLAT1; + pc = addIndex(program, pc, t.chr); + } + } + break; + case REOP_LPAREN: + pc = addIndex(program, pc, t.parenIndex); + pc = emitREBytecode(state, re, pc, t.kid); + program[pc++] = REOP_RPAREN; + pc = addIndex(program, pc, t.parenIndex); + break; + case REOP_BACKREF: + pc = addIndex(program, pc, t.parenIndex); + break; + case REOP_ASSERT: + nextTermFixup = pc; + pc += OFFSET_LEN; + pc = emitREBytecode(state, re, pc, t.kid); + program[pc++] = REOP_ASSERTTEST; + resolveForwardJump(program, nextTermFixup, pc); + break; + case REOP_ASSERT_NOT: + nextTermFixup = pc; + pc += OFFSET_LEN; + pc = emitREBytecode(state, re, pc, t.kid); + program[pc++] = REOP_ASSERTNOTTEST; + resolveForwardJump(program, nextTermFixup, pc); + break; + case REOP_QUANT: + if ((t.min == 0) && (t.max == -1)) + program[pc - 1] = (t.greedy) ? REOP_STAR : REOP_MINIMALSTAR; + else + if ((t.min == 0) && (t.max == 1)) + program[pc - 1] = (t.greedy) ? REOP_OPT : REOP_MINIMALOPT; + else + if ((t.min == 1) && (t.max == -1)) + program[pc - 1] = (t.greedy) ? REOP_PLUS : REOP_MINIMALPLUS; + else { + if (!t.greedy) program[pc - 1] = REOP_MINIMALQUANT; + pc = addIndex(program, pc, t.min); + // max can be -1 which addIndex does not accept + pc = addIndex(program, pc, t.max + 1); + } + pc = addIndex(program, pc, t.parenCount); + pc = addIndex(program, pc, t.parenIndex); + nextTermFixup = pc; + pc += OFFSET_LEN; + pc = emitREBytecode(state, re, pc, t.kid); + program[pc++] = REOP_ENDCHILD; + resolveForwardJump(program, nextTermFixup, pc); + break; + case REOP_CLASS: + pc = addIndex(program, pc, t.index); + re.classList[t.index] = new RECharSet(t.bmsize, t.startIndex, + t.kidlen); + break; + default: + break; + } + t = t.next; + } + return pc; + } + + private static void + pushProgState(REGlobalData gData, int min, int max, + REBackTrackData backTrackLastToSave, + int continuation_pc, int continuation_op) + { + gData.stateStackTop = new REProgState(gData.stateStackTop, min, max, + gData.cp, backTrackLastToSave, + continuation_pc, + continuation_op); + } + + private static REProgState + popProgState(REGlobalData gData) + { + REProgState state = gData.stateStackTop; + gData.stateStackTop = state.previous; + return state; + } + + private static void + pushBackTrackState(REGlobalData gData, byte op, int target) + { + gData.backTrackStackTop = new REBackTrackData(gData, op, target); + } + + /* + * Consecutive literal characters. + */ + private static boolean + flatNMatcher(REGlobalData gData, int matchChars, + int length, char[] chars, int end) + { + if ((gData.cp + length) > end) + return false; + for (int i = 0; i < length; i++) { + if (gData.regexp.source[matchChars + i] != chars[gData.cp + i]) { + return false; + } + } + gData.cp += length; + return true; + } + + private static boolean + flatNIMatcher(REGlobalData gData, int matchChars, + int length, char[] chars, int end) + { + if ((gData.cp + length) > end) + return false; + for (int i = 0; i < length; i++) { + if (upcase(gData.regexp.source[matchChars + i]) + != upcase(chars[gData.cp + i])) + { + return false; + } + } + gData.cp += length; + return true; + } + + /* + 1. Evaluate DecimalEscape to obtain an EscapeValue E. + 2. If E is not a character then go to step 6. + 3. Let ch be E's character. + 4. Let A be a one-element RECharSet containing the character ch. + 5. Call CharacterSetMatcher(A, false) and return its Matcher result. + 6. E must be an integer. Let n be that integer. + 7. If n=0 or n>NCapturingParens then throw a SyntaxError exception. + 8. Return an internal Matcher closure that takes two arguments, a State x + and a Continuation c, and performs the following: + 1. Let cap be x's captures internal array. + 2. Let s be cap[n]. + 3. If s is undefined, then call c(x) and return its result. + 4. Let e be x's endIndex. + 5. Let len be s's length. + 6. Let f be e+len. + 7. If f>InputLength, return failure. + 8. If there exists an integer i between 0 (inclusive) and len (exclusive) + such that Canonicalize(s[i]) is not the same character as + Canonicalize(Input [e+i]), then return failure. + 9. Let y be the State (f, cap). + 10. Call c(y) and return its result. + */ + private static boolean + backrefMatcher(REGlobalData gData, int parenIndex, + char[] chars, int end) + { + int len; + int i; + int parenContent = gData.parens_index(parenIndex); + if (parenContent == -1) + return true; + + len = gData.parens_length(parenIndex); + if ((gData.cp + len) > end) + return false; + + if ((gData.regexp.flags & JSREG_FOLD) != 0) { + for (i = 0; i < len; i++) { + if (upcase(chars[parenContent + i]) != upcase(chars[gData.cp + i])) + return false; + } + } + else { + for (i = 0; i < len; i++) { + if (chars[parenContent + i] != chars[gData.cp + i]) + return false; + } + } + gData.cp += len; + return true; + } + + + /* Add a single character to the RECharSet */ + private static void + addCharacterToCharSet(RECharSet cs, char c) + { + int byteIndex = (c / 8); + if (c > cs.length) + throw new RuntimeException(); + cs.bits[byteIndex] |= 1 << (c & 0x7); + } + + + /* Add a character range, c1 to c2 (inclusive) to the RECharSet */ + private static void + addCharacterRangeToCharSet(RECharSet cs, char c1, char c2) + { + int i; + + int byteIndex1 = (c1 / 8); + int byteIndex2 = (c2 / 8); + + if ((c2 > cs.length) || (c1 > c2)) + throw new RuntimeException(); + + c1 &= 0x7; + c2 &= 0x7; + + if (byteIndex1 == byteIndex2) { + cs.bits[byteIndex1] |= ((0xFF) >> (7 - (c2 - c1))) << c1; + } + else { + cs.bits[byteIndex1] |= 0xFF << c1; + for (i = byteIndex1 + 1; i < byteIndex2; i++) + cs.bits[i] = (byte)0xFF; + cs.bits[byteIndex2] |= (0xFF) >> (7 - c2); + } + } + + /* Compile the source of the class into a RECharSet */ + private static void + processCharSet(REGlobalData gData, RECharSet charSet) + { + synchronized (charSet) { + if (!charSet.converted) { + processCharSetImpl(gData, charSet); + charSet.converted = true; + } + } + } + + + private static void + processCharSetImpl(REGlobalData gData, RECharSet charSet) + { + int src = charSet.startIndex; + int end = src + charSet.strlength; + + char rangeStart = 0, thisCh; + int byteLength; + char c; + int n; + int nDigits; + int i; + boolean inRange = false; + + charSet.sense = true; + byteLength = (charSet.length / 8) + 1; + charSet.bits = new byte[byteLength]; + + if (src == end) + return; + + if (gData.regexp.source[src] == '^') { + charSet.sense = false; + ++src; + } + + while (src != end) { + nDigits = 2; + switch (gData.regexp.source[src]) { + case '\\': + ++src; + c = gData.regexp.source[src++]; + switch (c) { + case 'b': + thisCh = 0x8; + break; + case 'f': + thisCh = 0xC; + break; + case 'n': + thisCh = 0xA; + break; + case 'r': + thisCh = 0xD; + break; + case 't': + thisCh = 0x9; + break; + case 'v': + thisCh = 0xB; + break; + case 'c': + if (((src + 1) < end) && isWord(gData.regexp.source[src + 1])) + thisCh = (char)(gData.regexp.source[src++] & 0x1F); + else { + --src; + thisCh = '\\'; + } + break; + case 'u': + nDigits += 2; + // fall thru + case 'x': + n = 0; + for (i = 0; (i < nDigits) && (src < end); i++) { + c = gData.regexp.source[src++]; + int digit = toASCIIHexDigit(c); + if (digit < 0) { + /* back off to accepting the original '\' + * as a literal + */ + src -= (i + 1); + n = '\\'; + break; + } + n = (n << 4) | digit; + } + thisCh = (char)(n); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + /* + * This is a non-ECMA extension - decimal escapes (in this + * case, octal!) are supposed to be an error inside class + * ranges, but supported here for backwards compatibility. + * + */ + n = (c - '0'); + c = gData.regexp.source[src]; + if ('0' <= c && c <= '7') { + src++; + n = 8 * n + (c - '0'); + c = gData.regexp.source[src]; + if ('0' <= c && c <= '7') { + src++; + i = 8 * n + (c - '0'); + if (i <= 0377) + n = i; + else + src--; + } + } + thisCh = (char)(n); + break; + + case 'd': + addCharacterRangeToCharSet(charSet, '0', '9'); + continue; /* don't need range processing */ + case 'D': + addCharacterRangeToCharSet(charSet, (char)0, (char)('0' - 1)); + addCharacterRangeToCharSet(charSet, (char)('9' + 1), + (char)(charSet.length)); + continue; + case 's': + for (i = charSet.length; i >= 0; i--) + if (isREWhiteSpace(i)) + addCharacterToCharSet(charSet, (char)(i)); + continue; + case 'S': + for (i = charSet.length; i >= 0; i--) + if (!isREWhiteSpace(i)) + addCharacterToCharSet(charSet, (char)(i)); + continue; + case 'w': + for (i = charSet.length; i >= 0; i--) + if (isWord((char)i)) + addCharacterToCharSet(charSet, (char)(i)); + continue; + case 'W': + for (i = charSet.length; i >= 0; i--) + if (!isWord((char)i)) + addCharacterToCharSet(charSet, (char)(i)); + continue; + default: + thisCh = c; + break; + + } + break; + + default: + thisCh = gData.regexp.source[src++]; + break; + + } + if (inRange) { + if ((gData.regexp.flags & JSREG_FOLD) != 0) { + addCharacterRangeToCharSet(charSet, + upcase(rangeStart), + upcase(thisCh)); + addCharacterRangeToCharSet(charSet, + downcase(rangeStart), + downcase(thisCh)); + } else { + addCharacterRangeToCharSet(charSet, rangeStart, thisCh); + } + inRange = false; + } + else { + if ((gData.regexp.flags & JSREG_FOLD) != 0) { + addCharacterToCharSet(charSet, upcase(thisCh)); + addCharacterToCharSet(charSet, downcase(thisCh)); + } else { + addCharacterToCharSet(charSet, thisCh); + } + if (src < (end - 1)) { + if (gData.regexp.source[src] == '-') { + ++src; + inRange = true; + rangeStart = thisCh; + } + } + } + } + } + + + /* + * Initialize the character set if it this is the first call. + * Test the bit - if the ^ flag was specified, non-inclusion is a success + */ + private static boolean + classMatcher(REGlobalData gData, RECharSet charSet, char ch) + { + if (!charSet.converted) { + processCharSet(gData, charSet); + } + + int byteIndex = ch / 8; + if (charSet.sense) { + if ((charSet.length == 0) || + ( (ch > charSet.length) + || ((charSet.bits[byteIndex] & (1 << (ch & 0x7))) == 0) )) + return false; + } else { + if (! ((charSet.length == 0) || + ( (ch > charSet.length) + || ((charSet.bits[byteIndex] & (1 << (ch & 0x7))) == 0) ))) + return false; + } + return true; + } + + private static boolean + executeREBytecode(REGlobalData gData, char[] chars, int end) + { + int pc = 0; + byte program[] = gData.regexp.program; + int currentContinuation_op; + int currentContinuation_pc; + boolean result = false; + + currentContinuation_pc = 0; + currentContinuation_op = REOP_END; +if (debug) { +System.out.println("Input = \"" + new String(chars) + "\", start at " + gData.cp); +} + int op = program[pc++]; + for (;;) { +if (debug) { +System.out.println("Testing at " + gData.cp + ", op = " + op); +} + switch (op) { + case REOP_EMPTY: + result = true; + break; + case REOP_BOL: + if (gData.cp != 0) { + if (gData.multiline || + ((gData.regexp.flags & JSREG_MULTILINE) != 0)) { + if (!isLineTerm(chars[gData.cp - 1])) { + result = false; + break; + } + } + else { + result = false; + break; + } + } + result = true; + break; + case REOP_EOL: + if (gData.cp != end) { + if (gData.multiline || + ((gData.regexp.flags & JSREG_MULTILINE) != 0)) { + if (!isLineTerm(chars[gData.cp])) { + result = false; + break; + } + } + else { + result = false; + break; + } + } + result = true; + break; + case REOP_WBDRY: + result = ((gData.cp == 0 || !isWord(chars[gData.cp - 1])) + ^ !((gData.cp < end) && isWord(chars[gData.cp]))); + break; + case REOP_WNONBDRY: + result = ((gData.cp == 0 || !isWord(chars[gData.cp - 1])) + ^ ((gData.cp < end) && isWord(chars[gData.cp]))); + break; + case REOP_DOT: + result = (gData.cp != end && !isLineTerm(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_DIGIT: + result = (gData.cp != end && isDigit(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_NONDIGIT: + result = (gData.cp != end && !isDigit(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_SPACE: + result = (gData.cp != end && isREWhiteSpace(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_NONSPACE: + result = (gData.cp != end && !isREWhiteSpace(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_ALNUM: + result = (gData.cp != end && isWord(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_NONALNUM: + result = (gData.cp != end && !isWord(chars[gData.cp])); + if (result) { + gData.cp++; + } + break; + case REOP_FLAT: + { + int offset = getIndex(program, pc); + pc += INDEX_LEN; + int length = getIndex(program, pc); + pc += INDEX_LEN; + result = flatNMatcher(gData, offset, length, chars, end); + } + break; + case REOP_FLATi: + { + int offset = getIndex(program, pc); + pc += INDEX_LEN; + int length = getIndex(program, pc); + pc += INDEX_LEN; + result = flatNIMatcher(gData, offset, length, chars, end); + } + break; + case REOP_FLAT1: + { + char matchCh = (char)(program[pc++] & 0xFF); + result = (gData.cp != end && chars[gData.cp] == matchCh); + if (result) { + gData.cp++; + } + } + break; + case REOP_FLAT1i: + { + char matchCh = (char)(program[pc++] & 0xFF); + result = (gData.cp != end + && upcase(chars[gData.cp]) == upcase(matchCh)); + if (result) { + gData.cp++; + } + } + break; + case REOP_UCFLAT1: + { + char matchCh = (char)getIndex(program, pc); + pc += INDEX_LEN; + result = (gData.cp != end && chars[gData.cp] == matchCh); + if (result) { + gData.cp++; + } + } + break; + case REOP_UCFLAT1i: + { + char matchCh = (char)getIndex(program, pc); + pc += INDEX_LEN; + result = (gData.cp != end + && upcase(chars[gData.cp]) == upcase(matchCh)); + if (result) { + gData.cp++; + } + } + break; + case REOP_ALT: + { + int nextpc; + byte nextop; + pushProgState(gData, 0, 0, null, + currentContinuation_pc, + currentContinuation_op); + nextpc = pc + getOffset(program, pc); + nextop = program[nextpc++]; + pushBackTrackState(gData, nextop, nextpc); + pc += INDEX_LEN; + op = program[pc++]; + } + continue; + + case REOP_JUMP: + { + int offset; + REProgState state = popProgState(gData); + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + offset = getOffset(program, pc); + pc += offset; + op = program[pc++]; + } + continue; + + + case REOP_LPAREN: + { + int parenIndex = getIndex(program, pc); + pc += INDEX_LEN; + gData.set_parens(parenIndex, gData.cp, 0); + op = program[pc++]; + } + continue; + case REOP_RPAREN: + { + int cap_index; + int parenIndex = getIndex(program, pc); + pc += INDEX_LEN; + cap_index = gData.parens_index(parenIndex); + gData.set_parens(parenIndex, cap_index, + gData.cp - cap_index); + if (parenIndex > gData.lastParen) + gData.lastParen = parenIndex; + op = program[pc++]; + } + continue; + case REOP_BACKREF: + { + int parenIndex = getIndex(program, pc); + pc += INDEX_LEN; + result = backrefMatcher(gData, parenIndex, chars, end); + } + break; + + case REOP_CLASS: + { + int index = getIndex(program, pc); + pc += INDEX_LEN; + if (gData.cp != end) { + if (classMatcher(gData, gData.regexp.classList[index], + chars[gData.cp])) + { + gData.cp++; + result = true; + break; + } + } + result = false; + } + break; + + case REOP_ASSERT: + case REOP_ASSERT_NOT: + { + byte testOp; + pushProgState(gData, 0, 0, gData.backTrackStackTop, + currentContinuation_pc, + currentContinuation_op); + if (op == REOP_ASSERT) { + testOp = REOP_ASSERTTEST; + } else { + testOp = REOP_ASSERTNOTTEST; + } + pushBackTrackState(gData, testOp, + pc + getOffset(program, pc)); + pc += INDEX_LEN; + op = program[pc++]; + } + continue; + + case REOP_ASSERTTEST: + case REOP_ASSERTNOTTEST: + { + REProgState state = popProgState(gData); + gData.cp = state.index; + gData.backTrackStackTop = state.backTrack; + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + if (result) { + if (op == REOP_ASSERTTEST) { + result = true; + } else { + result = false; + } + } else { + if (op == REOP_ASSERTTEST) { + // Do nothing + } else { + result = true; + } + } + } + break; + + case REOP_STAR: + case REOP_PLUS: + case REOP_OPT: + case REOP_QUANT: + case REOP_MINIMALSTAR: + case REOP_MINIMALPLUS: + case REOP_MINIMALOPT: + case REOP_MINIMALQUANT: + { + int min, max; + boolean greedy = false; + switch (op) { + case REOP_STAR: + greedy = true; + // fallthrough + case REOP_MINIMALSTAR: + min = 0; + max = -1; + break; + case REOP_PLUS: + greedy = true; + // fallthrough + case REOP_MINIMALPLUS: + min = 1; + max = -1; + break; + case REOP_OPT: + greedy = true; + // fallthrough + case REOP_MINIMALOPT: + min = 0; + max = 1; + break; + case REOP_QUANT: + greedy = true; + // fallthrough + case REOP_MINIMALQUANT: + min = getOffset(program, pc); + pc += INDEX_LEN; + // See comments in emitREBytecode for " - 1" reason + max = getOffset(program, pc) - 1; + pc += INDEX_LEN; + break; + default: + throw Kit.codeBug(); + } + pushProgState(gData, min, max, null, + currentContinuation_pc, + currentContinuation_op); + if (greedy) { + currentContinuation_op = REOP_REPEAT; + currentContinuation_pc = pc; + pushBackTrackState(gData, REOP_REPEAT, pc); + /* Step over <parencount>, <parenindex> & <next> */ + pc += 3 * INDEX_LEN; + op = program[pc++]; + } else { + if (min != 0) { + currentContinuation_op = REOP_MINIMALREPEAT; + currentContinuation_pc = pc; + /* <parencount> <parenindex> & <next> */ + pc += 3 * INDEX_LEN; + op = program[pc++]; + } else { + pushBackTrackState(gData, REOP_MINIMALREPEAT, pc); + popProgState(gData); + pc += 2 * INDEX_LEN; // <parencount> & <parenindex> + pc = pc + getOffset(program, pc); + op = program[pc++]; + } + } + } + continue; + + case REOP_ENDCHILD: + // Use the current continuation. + pc = currentContinuation_pc; + op = currentContinuation_op; + continue; + + case REOP_REPEAT: + { + REProgState state = popProgState(gData); + if (!result) { + // + // There's been a failure, see if we have enough + // children. + // + if (state.min == 0) + result = true; + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + pc += 2 * INDEX_LEN; /* <parencount> & <parenindex> */ + pc = pc + getOffset(program, pc); + break; + } + else { + if (state.min == 0 && gData.cp == state.index) { + // matched an empty string, that'll get us nowhere + result = false; + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + pc += 2 * INDEX_LEN; + pc = pc + getOffset(program, pc); + break; + } + int new_min = state.min, new_max = state.max; + if (new_min != 0) new_min--; + if (new_max != -1) new_max--; + if (new_max == 0) { + result = true; + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + pc += 2 * INDEX_LEN; + pc = pc + getOffset(program, pc); + break; + } + pushProgState(gData, new_min, new_max, null, + state.continuation_pc, + state.continuation_op); + currentContinuation_op = REOP_REPEAT; + currentContinuation_pc = pc; + pushBackTrackState(gData, REOP_REPEAT, pc); + int parenCount = getIndex(program, pc); + pc += INDEX_LEN; + int parenIndex = getIndex(program, pc); + pc += 2 * INDEX_LEN; + op = program[pc++]; + for (int k = 0; k < parenCount; k++) { + gData.set_parens(parenIndex + k, -1, 0); + } + } + } + continue; + + case REOP_MINIMALREPEAT: + { + REProgState state = popProgState(gData); + if (!result) { + // + // Non-greedy failure - try to consume another child. + // + if (state.max == -1 || state.max > 0) { + pushProgState(gData, state.min, state.max, null, + state.continuation_pc, + state.continuation_op); + currentContinuation_op = REOP_MINIMALREPEAT; + currentContinuation_pc = pc; + int parenCount = getIndex(program, pc); + pc += INDEX_LEN; + int parenIndex = getIndex(program, pc); + pc += 2 * INDEX_LEN; + for (int k = 0; k < parenCount; k++) { + gData.set_parens(parenIndex + k, -1, 0); + } + op = program[pc++]; + continue; + } else { + // Don't need to adjust pc since we're going to pop. + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + break; + } + } else { + if (state.min == 0 && gData.cp == state.index) { + // Matched an empty string, that'll get us nowhere. + result = false; + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + break; + } + int new_min = state.min, new_max = state.max; + if (new_min != 0) new_min--; + if (new_max != -1) new_max--; + pushProgState(gData, new_min, new_max, null, + state.continuation_pc, + state.continuation_op); + if (new_min != 0) { + currentContinuation_op = REOP_MINIMALREPEAT; + currentContinuation_pc = pc; + int parenCount = getIndex(program, pc); + pc += INDEX_LEN; + int parenIndex = getIndex(program, pc); + pc += 2 * INDEX_LEN; + for (int k = 0; k < parenCount; k++) { + gData.set_parens(parenIndex + k, -1, 0); + } + op = program[pc++]; + } else { + currentContinuation_pc = state.continuation_pc; + currentContinuation_op = state.continuation_op; + pushBackTrackState(gData, REOP_MINIMALREPEAT, pc); + popProgState(gData); + pc += 2 * INDEX_LEN; + pc = pc + getOffset(program, pc); + op = program[pc++]; + } + continue; + } + } + + case REOP_END: + return true; + + default: + throw Kit.codeBug(); + + } + /* + * If the match failed and there's a backtrack option, take it. + * Otherwise this is a complete and utter failure. + */ + if (!result) { + REBackTrackData backTrackData = gData.backTrackStackTop; + if (backTrackData != null) { + gData.backTrackStackTop = backTrackData.previous; + + gData.lastParen = backTrackData.lastParen; + + // XXX: If backTrackData will no longer be used, then + // there is no need to clone backTrackData.parens + if (backTrackData.parens != null) { + gData.parens = backTrackData.parens.clone(); + } + + gData.cp = backTrackData.cp; + + gData.stateStackTop = backTrackData.stateStackTop; + + currentContinuation_op + = gData.stateStackTop.continuation_op; + currentContinuation_pc + = gData.stateStackTop.continuation_pc; + pc = backTrackData.continuation_pc; + op = backTrackData.continuation_op; + continue; + } + else + return false; + } + + op = program[pc++]; + } + + } + + private static boolean + matchRegExp(REGlobalData gData, RECompiled re, + char[] chars, int start, int end, boolean multiline) + { + if (re.parenCount != 0) { + gData.parens = new long[re.parenCount]; + } else { + gData.parens = null; + } + + gData.backTrackStackTop = null; + + gData.stateStackTop = null; + + gData.multiline = multiline; + gData.regexp = re; + gData.lastParen = 0; + + int anchorCh = gData.regexp.anchorCh; + // + // have to include the position beyond the last character + // in order to detect end-of-input/line condition + // + for (int i = start; i <= end; ++i) { + // + // If the first node is a literal match, step the index into + // the string until that match is made, or fail if it can't be + // found at all. + // + if (anchorCh >= 0) { + for (;;) { + if (i == end) { + return false; + } + char matchCh = chars[i]; + if (matchCh == anchorCh || + ((gData.regexp.flags & JSREG_FOLD) != 0 + && upcase(matchCh) == upcase((char)anchorCh))) + { + break; + } + ++i; + } + } + gData.cp = i; + for (int j = 0; j < re.parenCount; j++) { + gData.set_parens(j, -1, 0); + } + boolean result = executeREBytecode(gData, chars, end); + + gData.backTrackStackTop = null; + gData.stateStackTop = null; + if (result) { + gData.skipped = i - start; + return true; + } + } + return false; + } + + /* + * indexp is assumed to be an array of length 1 + */ + Object executeRegExp(Context cx, Scriptable scopeObj, RegExpImpl res, + String str, int indexp[], int matchType) + { + REGlobalData gData = new REGlobalData(); + + int start = indexp[0]; + char[] charArray = str.toCharArray(); + int end = charArray.length; + if (start > end) + start = end; + // + // Call the recursive matcher to do the real work. + // + boolean matches = matchRegExp(gData, re, charArray, start, end, + res.multiline); + if (!matches) { + if (matchType != PREFIX) return null; + return Undefined.instance; + } + int index = gData.cp; + int i = index; + indexp[0] = i; + int matchlen = i - (start + gData.skipped); + int ep = index; + index -= matchlen; + Object result; + Scriptable obj; + + if (matchType == TEST) { + /* + * Testing for a match and updating cx.regExpImpl: don't allocate + * an array object, do return true. + */ + result = Boolean.TRUE; + obj = null; + } + else { + /* + * The array returned on match has element 0 bound to the matched + * string, elements 1 through re.parenCount bound to the paren + * matches, an index property telling the length of the left context, + * and an input property referring to the input string. + */ + Scriptable scope = getTopLevelScope(scopeObj); + result = ScriptRuntime.newObject(cx, scope, "Array", null); + obj = (Scriptable) result; + + String matchstr = new String(charArray, index, matchlen); + obj.put(0, obj, matchstr); + } + + if (re.parenCount == 0) { + res.parens = null; + res.lastParen = SubString.emptySubString; + } else { + SubString parsub = null; + int num; + res.parens = new SubString[re.parenCount]; + for (num = 0; num < re.parenCount; num++) { + int cap_index = gData.parens_index(num); + String parstr; + if (cap_index != -1) { + int cap_length = gData.parens_length(num); + parsub = new SubString(charArray, cap_index, cap_length); + res.parens[num] = parsub; + if (matchType == TEST) continue; + parstr = parsub.toString(); + obj.put(num+1, obj, parstr); + } + else { + if (matchType != TEST) + obj.put(num+1, obj, Undefined.instance); + } + } + res.lastParen = parsub; + } + + if (! (matchType == TEST)) { + /* + * Define the index and input properties last for better for/in loop + * order (so they come after the elements). + */ + obj.put("index", obj, new Integer(start + gData.skipped)); + obj.put("input", obj, str); + } + + if (res.lastMatch == null) { + res.lastMatch = new SubString(); + res.leftContext = new SubString(); + res.rightContext = new SubString(); + } + res.lastMatch.charArray = charArray; + res.lastMatch.index = index; + res.lastMatch.length = matchlen; + + res.leftContext.charArray = charArray; + if (cx.getLanguageVersion() == Context.VERSION_1_2) { + /* + * JS1.2 emulated Perl4.0.1.8 (patch level 36) for global regexps used + * in scalar contexts, and unintentionally for the string.match "list" + * psuedo-context. On "hi there bye", the following would result: + * + * Language while(/ /g){print("$`");} s/ /$`/g + * perl4.036 "hi", "there" "hihitherehi therebye" + * perl5 "hi", "hi there" "hihitherehi therebye" + * js1.2 "hi", "there" "hihitheretherebye" + * + * Insofar as JS1.2 always defined $` as "left context from the last + * match" for global regexps, it was more consistent than perl4. + */ + res.leftContext.index = start; + res.leftContext.length = gData.skipped; + } else { + /* + * For JS1.3 and ECMAv2, emulate Perl5 exactly: + * + * js1.3 "hi", "hi there" "hihitherehi therebye" + */ + res.leftContext.index = 0; + res.leftContext.length = start + gData.skipped; + } + + res.rightContext.charArray = charArray; + res.rightContext.index = ep; + res.rightContext.length = end - ep; + + return result; + } + + int getFlags() + { + return re.flags; + } + + private static void reportWarning(Context cx, String messageId, String arg) + { + if (cx.hasFeature(Context.FEATURE_STRICT_MODE)) { + String msg = ScriptRuntime.getMessage1(messageId, arg); + Context.reportWarning(msg); + } + } + + private static void reportError(String messageId, String arg) + { + String msg = ScriptRuntime.getMessage1(messageId, arg); + throw ScriptRuntime.constructError("SyntaxError", msg); + } + +// #string_id_map# + + private static final int + Id_lastIndex = 1, + Id_source = 2, + Id_global = 3, + Id_ignoreCase = 4, + Id_multiline = 5, + + MAX_INSTANCE_ID = 5; + + protected int getMaxInstanceId() + { + return MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:16:24 EDT + L0: { id = 0; String X = null; int c; + int s_length = s.length(); + if (s_length==6) { + c=s.charAt(0); + if (c=='g') { X="global";id=Id_global; } + else if (c=='s') { X="source";id=Id_source; } + } + else if (s_length==9) { + c=s.charAt(0); + if (c=='l') { X="lastIndex";id=Id_lastIndex; } + else if (c=='m') { X="multiline";id=Id_multiline; } + } + else if (s_length==10) { X="ignoreCase";id=Id_ignoreCase; } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# +// #/string_id_map# + + if (id == 0) return super.findInstanceIdInfo(s); + + int attr; + switch (id) { + case Id_lastIndex: + attr = PERMANENT | DONTENUM; + break; + case Id_source: + case Id_global: + case Id_ignoreCase: + case Id_multiline: + attr = PERMANENT | READONLY | DONTENUM; + break; + default: + throw new IllegalStateException(); + } + return instanceIdInfo(attr, id); + } + + protected String getInstanceIdName(int id) + { + switch (id) { + case Id_lastIndex: return "lastIndex"; + case Id_source: return "source"; + case Id_global: return "global"; + case Id_ignoreCase: return "ignoreCase"; + case Id_multiline: return "multiline"; + } + return super.getInstanceIdName(id); + } + + protected Object getInstanceIdValue(int id) + { + switch (id) { + case Id_lastIndex: + return ScriptRuntime.wrapNumber(lastIndex); + case Id_source: + return new String(re.source); + case Id_global: + return ScriptRuntime.wrapBoolean((re.flags & JSREG_GLOB) != 0); + case Id_ignoreCase: + return ScriptRuntime.wrapBoolean((re.flags & JSREG_FOLD) != 0); + case Id_multiline: + return ScriptRuntime.wrapBoolean((re.flags & JSREG_MULTILINE) != 0); + } + return super.getInstanceIdValue(id); + } + + protected void setInstanceIdValue(int id, Object value) + { + if (id == Id_lastIndex) { + lastIndex = ScriptRuntime.toNumber(value); + return; + } + super.setInstanceIdValue(id, value); + } + + protected void initPrototypeId(int id) + { + String s; + int arity; + switch (id) { + case Id_compile: arity=1; s="compile"; break; + case Id_toString: arity=0; s="toString"; break; + case Id_toSource: arity=0; s="toSource"; break; + case Id_exec: arity=1; s="exec"; break; + case Id_test: arity=1; s="test"; break; + case Id_prefix: arity=1; s="prefix"; break; + default: throw new IllegalArgumentException(String.valueOf(id)); + } + initPrototypeMethod(REGEXP_TAG, id, s, arity); + } + + public Object execIdCall(IdFunctionObject f, Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + { + if (!f.hasTag(REGEXP_TAG)) { + return super.execIdCall(f, cx, scope, thisObj, args); + } + int id = f.methodId(); + switch (id) { + case Id_compile: + return realThis(thisObj, f).compile(cx, scope, args); + + case Id_toString: + case Id_toSource: + return realThis(thisObj, f).toString(); + + case Id_exec: + return realThis(thisObj, f).execSub(cx, scope, args, MATCH); + + case Id_test: { + Object x = realThis(thisObj, f).execSub(cx, scope, args, TEST); + return Boolean.TRUE.equals(x) ? Boolean.TRUE : Boolean.FALSE; + } + + case Id_prefix: + return realThis(thisObj, f).execSub(cx, scope, args, PREFIX); + } + throw new IllegalArgumentException(String.valueOf(id)); + } + + private static NativeRegExp realThis(Scriptable thisObj, IdFunctionObject f) + { + if (!(thisObj instanceof NativeRegExp)) + throw incompatibleCallError(f); + return (NativeRegExp)thisObj; + } + +// #string_id_map# + protected int findPrototypeId(String s) + { + int id; +// #generated# Last update: 2007-05-09 08:16:24 EDT + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 4: c=s.charAt(0); + if (c=='e') { X="exec";id=Id_exec; } + else if (c=='t') { X="test";id=Id_test; } + break L; + case 6: X="prefix";id=Id_prefix; break L; + case 7: X="compile";id=Id_compile; break L; + case 8: c=s.charAt(3); + if (c=='o') { X="toSource";id=Id_toSource; } + else if (c=='t') { X="toString";id=Id_toString; } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + break L0; + } +// #/generated# + return id; + } + + private static final int + Id_compile = 1, + Id_toString = 2, + Id_toSource = 3, + Id_exec = 4, + Id_test = 5, + Id_prefix = 6, + + MAX_PROTOTYPE_ID = 6; + +// #/string_id_map# + + private RECompiled re; + double lastIndex; /* index after last match, for //g iterator */ + +} // class NativeRegExp + +class RECompiled implements Serializable +{ + static final long serialVersionUID = -6144956577595844213L; + + char []source; /* locked source string, sans // */ + int parenCount; /* number of parenthesized submatches */ + int flags; /* flags */ + byte[] program; /* regular expression bytecode */ + int classCount; /* count [...] bitmaps */ + RECharSet[] classList; /* list of [...] bitmaps */ + int anchorCh = -1; /* if >= 0, then re starts with this literal char */ +} + +class RENode { + + RENode(byte op) + { + this.op = op; + } + + byte op; /* r.e. op bytecode */ + RENode next; /* next in concatenation order */ + RENode kid; /* first operand */ + + RENode kid2; /* second operand */ + int num; /* could be a number */ + int parenIndex; /* or a parenthesis index */ + + /* or a range */ + int min; + int max; + int parenCount; + boolean greedy; + + /* or a character class */ + int startIndex; + int kidlen; /* length of string at kid, in chars */ + int bmsize; /* bitmap size, based on max char code */ + int index; /* index into class list */ + + /* or a literal sequence */ + char chr; /* of one character */ + int length; /* or many (via the index) */ + int flatIndex; /* which is -1 if not sourced */ + +} + +class CompilerState { + + CompilerState(Context cx, char[] source, int length, int flags) + { + this.cx = cx; + this.cpbegin = source; + this.cp = 0; + this.cpend = length; + this.flags = flags; + this.parenCount = 0; + this.classCount = 0; + this.progLength = 0; + } + + Context cx; + char cpbegin[]; + int cpend; + int cp; + int flags; + int parenCount; + int parenNesting; + int classCount; /* number of [] encountered */ + int progLength; /* estimated bytecode length */ + RENode result; +} + +class REProgState +{ + REProgState(REProgState previous, int min, int max, int index, + REBackTrackData backTrack, + int continuation_pc, int continuation_op) + { + this.previous = previous; + this.min = min; + this.max = max; + this.index = index; + this.continuation_op = continuation_op; + this.continuation_pc = continuation_pc; + this.backTrack = backTrack; + } + + REProgState previous; // previous state in stack + + int min; /* current quantifier min */ + int max; /* current quantifier max */ + int index; /* progress in text */ + int continuation_op; + int continuation_pc; + REBackTrackData backTrack; // used by ASSERT_ to recover state +} + +class REBackTrackData { + + REBackTrackData(REGlobalData gData, int op, int pc) + { + previous = gData.backTrackStackTop; + continuation_op = op; + continuation_pc = pc; + lastParen = gData.lastParen; + if (gData.parens != null) { + parens = gData.parens.clone(); + } + cp = gData.cp; + stateStackTop = gData.stateStackTop; + } + + REBackTrackData previous; + + int continuation_op; /* where to backtrack to */ + int continuation_pc; + int lastParen; + long[] parens; /* parenthesis captures */ + int cp; /* char buffer index */ + REProgState stateStackTop; /* state of op that backtracked */ +} + +class REGlobalData { + boolean multiline; + RECompiled regexp; /* the RE in execution */ + int lastParen; /* highest paren set so far */ + int skipped; /* chars skipped anchoring this r.e. */ + + int cp; /* char buffer index */ + long[] parens; /* parens captures */ + + REProgState stateStackTop; /* stack of state of current ancestors */ + + REBackTrackData backTrackStackTop; /* last matched-so-far position */ + + + /** + * Get start of parenthesis capture contents, -1 for empty. + */ + int parens_index(int i) + { + return (int)(parens[i]); + } + + /** + * Get length of parenthesis capture contents. + */ + int parens_length(int i) + { + return (int)(parens[i] >>> 32); + } + + void set_parens(int i, int index, int length) + { + parens[i] = (index & 0xffffffffL) | ((long)length << 32); + } + +} + +/* + * This struct holds a bitmap representation of a class from a regexp. + * There's a list of these referenced by the classList field in the NativeRegExp + * struct below. The initial state has startIndex set to the offset in the + * original regexp source of the beginning of the class contents. The first + * use of the class converts the source representation into a bitmap. + * + */ +final class RECharSet implements Serializable +{ + static final long serialVersionUID = 7931787979395898394L; + + RECharSet(int length, int startIndex, int strlength) + { + this.length = length; + this.startIndex = startIndex; + this.strlength = strlength; + } + + int length; + int startIndex; + int strlength; + + volatile transient boolean converted; + volatile transient boolean sense; + volatile transient byte[] bits; +} + + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java new file mode 100644 index 0000000..808d62d --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/NativeRegExpCtor.java @@ -0,0 +1,289 @@ +/* -*- 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, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Brendan Eich + * + * 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.regexp; + +import org.mozilla.javascript.*; + +/** + * This class implements the RegExp constructor native object. + * + * Revision History: + * Implementation in C by Brendan Eich + * Initial port to Java by Norris Boyd from jsregexp.c version 1.36 + * Merged up to version 1.38, which included Unicode support. + * Merged bug fixes in version 1.39. + * Merged JSFUN13_BRANCH changes up to 1.32.2.11 + * + * @author Brendan Eich + * @author Norris Boyd + */ +class NativeRegExpCtor extends BaseFunction +{ + static final long serialVersionUID = -5733330028285400526L; + + NativeRegExpCtor() + { + } + + public String getFunctionName() + { + return "RegExp"; + } + + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + { + if (args.length > 0 && args[0] instanceof NativeRegExp && + (args.length == 1 || args[1] == Undefined.instance)) + { + return args[0]; + } + return construct(cx, scope, args); + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + { + NativeRegExp re = new NativeRegExp(); + re.compile(cx, scope, args); + ScriptRuntime.setObjectProtoAndParent(re, scope); + return re; + } + + private static RegExpImpl getImpl() + { + Context cx = Context.getCurrentContext(); + return (RegExpImpl) ScriptRuntime.getRegExpProxy(cx); + } + +// #string_id_map# + + private static final int + Id_multiline = 1, + Id_STAR = 2, // #string=$*# + + Id_input = 3, + Id_UNDERSCORE = 4, // #string=$_# + + Id_lastMatch = 5, + Id_AMPERSAND = 6, // #string=$&# + + Id_lastParen = 7, + Id_PLUS = 8, // #string=$+# + + Id_leftContext = 9, + Id_BACK_QUOTE = 10, // #string=$`# + + Id_rightContext = 11, + Id_QUOTE = 12, // #string=$'# + + DOLLAR_ID_BASE = 12; + + private static final int + Id_DOLLAR_1 = DOLLAR_ID_BASE + 1, // #string=$1# + Id_DOLLAR_2 = DOLLAR_ID_BASE + 2, // #string=$2# + Id_DOLLAR_3 = DOLLAR_ID_BASE + 3, // #string=$3# + Id_DOLLAR_4 = DOLLAR_ID_BASE + 4, // #string=$4# + Id_DOLLAR_5 = DOLLAR_ID_BASE + 5, // #string=$5# + Id_DOLLAR_6 = DOLLAR_ID_BASE + 6, // #string=$6# + Id_DOLLAR_7 = DOLLAR_ID_BASE + 7, // #string=$7# + Id_DOLLAR_8 = DOLLAR_ID_BASE + 8, // #string=$8# + Id_DOLLAR_9 = DOLLAR_ID_BASE + 9, // #string=$9# + + MAX_INSTANCE_ID = DOLLAR_ID_BASE + 9; + + protected int getMaxInstanceId() + { + return super.getMaxInstanceId() + MAX_INSTANCE_ID; + } + + protected int findInstanceIdInfo(String s) { + int id; +// #generated# Last update: 2001-05-24 16:09:31 GMT+02:00 + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 2: switch (s.charAt(1)) { + case '&': if (s.charAt(0)=='$') {id=Id_AMPERSAND; break L0;} break L; + case '\'': if (s.charAt(0)=='$') {id=Id_QUOTE; break L0;} break L; + case '*': if (s.charAt(0)=='$') {id=Id_STAR; break L0;} break L; + case '+': if (s.charAt(0)=='$') {id=Id_PLUS; break L0;} break L; + case '1': if (s.charAt(0)=='$') {id=Id_DOLLAR_1; break L0;} break L; + case '2': if (s.charAt(0)=='$') {id=Id_DOLLAR_2; break L0;} break L; + case '3': if (s.charAt(0)=='$') {id=Id_DOLLAR_3; break L0;} break L; + case '4': if (s.charAt(0)=='$') {id=Id_DOLLAR_4; break L0;} break L; + case '5': if (s.charAt(0)=='$') {id=Id_DOLLAR_5; break L0;} break L; + case '6': if (s.charAt(0)=='$') {id=Id_DOLLAR_6; break L0;} break L; + case '7': if (s.charAt(0)=='$') {id=Id_DOLLAR_7; break L0;} break L; + case '8': if (s.charAt(0)=='$') {id=Id_DOLLAR_8; break L0;} break L; + case '9': if (s.charAt(0)=='$') {id=Id_DOLLAR_9; break L0;} break L; + case '_': if (s.charAt(0)=='$') {id=Id_UNDERSCORE; break L0;} break L; + case '`': if (s.charAt(0)=='$') {id=Id_BACK_QUOTE; break L0;} break L; + } break L; + case 5: X="input";id=Id_input; break L; + case 9: c=s.charAt(4); + if (c=='M') { X="lastMatch";id=Id_lastMatch; } + else if (c=='P') { X="lastParen";id=Id_lastParen; } + else if (c=='i') { X="multiline";id=Id_multiline; } + break L; + case 11: X="leftContext";id=Id_leftContext; break L; + case 12: X="rightContext";id=Id_rightContext; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + + if (id == 0) return super.findInstanceIdInfo(s); + + int attr; + switch (id) { + case Id_multiline: + case Id_STAR: + case Id_input: + case Id_UNDERSCORE: + attr = PERMANENT; + break; + default: + attr = PERMANENT | READONLY; + break; + } + + return instanceIdInfo(attr, super.getMaxInstanceId() + id); + } + +// #/string_id_map# + + protected String getInstanceIdName(int id) + { + int shifted = id - super.getMaxInstanceId(); + if (1 <= shifted && shifted <= MAX_INSTANCE_ID) { + switch (shifted) { + case Id_multiline: return "multiline"; + case Id_STAR: return "$*"; + + case Id_input: return "input"; + case Id_UNDERSCORE: return "$_"; + + case Id_lastMatch: return "lastMatch"; + case Id_AMPERSAND: return "$&"; + + case Id_lastParen: return "lastParen"; + case Id_PLUS: return "$+"; + + case Id_leftContext: return "leftContext"; + case Id_BACK_QUOTE: return "$`"; + + case Id_rightContext: return "rightContext"; + case Id_QUOTE: return "$'"; + } + // Must be one of $1..$9, convert to 0..8 + int substring_number = shifted - DOLLAR_ID_BASE - 1; + char[] buf = { '$', (char)('1' + substring_number) }; + return new String(buf); + } + return super.getInstanceIdName(id); + } + + protected Object getInstanceIdValue(int id) + { + int shifted = id - super.getMaxInstanceId(); + if (1 <= shifted && shifted <= MAX_INSTANCE_ID) { + RegExpImpl impl = getImpl(); + Object stringResult; + switch (shifted) { + case Id_multiline: + case Id_STAR: + return ScriptRuntime.wrapBoolean(impl.multiline); + + case Id_input: + case Id_UNDERSCORE: + stringResult = impl.input; + break; + + case Id_lastMatch: + case Id_AMPERSAND: + stringResult = impl.lastMatch; + break; + + case Id_lastParen: + case Id_PLUS: + stringResult = impl.lastParen; + break; + + case Id_leftContext: + case Id_BACK_QUOTE: + stringResult = impl.leftContext; + break; + + case Id_rightContext: + case Id_QUOTE: + stringResult = impl.rightContext; + break; + + default: + { + // Must be one of $1..$9, convert to 0..8 + int substring_number = shifted - DOLLAR_ID_BASE - 1; + stringResult = impl.getParenSubString(substring_number); + break; + } + } + return (stringResult == null) ? "" : stringResult.toString(); + } + return super.getInstanceIdValue(id); + } + + protected void setInstanceIdValue(int id, Object value) + { + int shifted = id - super.getMaxInstanceId(); + switch (shifted) { + case Id_multiline: + case Id_STAR: + getImpl().multiline = ScriptRuntime.toBoolean(value); + return; + + case Id_input: + case Id_UNDERSCORE: + getImpl().input = ScriptRuntime.toString(value); + return; + } + super.setInstanceIdValue(id, value); + } + +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/RegExpImpl.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/RegExpImpl.java new file mode 100644 index 0000000..4b0a303 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/RegExpImpl.java @@ -0,0 +1,541 @@ +/* -*- 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, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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.regexp; + +import org.mozilla.javascript.*; + +/** + * + */ +public class RegExpImpl implements RegExpProxy { + + public boolean isRegExp(Scriptable obj) { + return obj instanceof NativeRegExp; + } + + public Object compileRegExp(Context cx, String source, String flags) + { + return NativeRegExp.compileRE(cx, source, flags, false); + } + + public Scriptable wrapRegExp(Context cx, Scriptable scope, + Object compiled) + { + return new NativeRegExp(scope, compiled); + } + + public Object action(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args, + int actionType) + { + GlobData data = new GlobData(); + data.mode = actionType; + + switch (actionType) { + case RA_MATCH: + { + Object rval; + data.optarg = 1; + rval = matchOrReplace(cx, scope, thisObj, args, + this, data, false); + return data.arrayobj == null ? rval : data.arrayobj; + } + + case RA_SEARCH: + data.optarg = 1; + return matchOrReplace(cx, scope, thisObj, args, + this, data, false); + + case RA_REPLACE: + { + Object arg1 = args.length < 2 ? Undefined.instance : args[1]; + String repstr = null; + Function lambda = null; + if (arg1 instanceof Function) { + lambda = (Function) arg1; + } else { + repstr = ScriptRuntime.toString(arg1); + } + + data.optarg = 2; + data.lambda = lambda; + data.repstr = repstr; + data.dollar = repstr == null ? -1 : repstr.indexOf('$'); + data.charBuf = null; + data.leftIndex = 0; + Object val = matchOrReplace(cx, scope, thisObj, args, + this, data, true); + SubString rc = this.rightContext; + + if (data.charBuf == null) { + if (data.global || val == null + || !val.equals(Boolean.TRUE)) + { + /* Didn't match even once. */ + return data.str; + } + SubString lc = this.leftContext; + replace_glob(data, cx, scope, this, lc.index, lc.length); + } + data.charBuf.append(rc.charArray, rc.index, rc.length); + return data.charBuf.toString(); + } + + default: + throw Kit.codeBug(); + } + } + + /** + * Analog of C match_or_replace. + */ + private static Object matchOrReplace(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args, + RegExpImpl reImpl, + GlobData data, boolean forceFlat) + { + NativeRegExp re; + + String str = ScriptRuntime.toString(thisObj); + data.str = str; + Scriptable topScope = ScriptableObject.getTopLevelScope(scope); + + if (args.length == 0) { + Object compiled = NativeRegExp.compileRE(cx, "", "", false); + re = new NativeRegExp(topScope, compiled); + } else if (args[0] instanceof NativeRegExp) { + re = (NativeRegExp) args[0]; + } else { + String src = ScriptRuntime.toString(args[0]); + String opt; + if (data.optarg < args.length) { + args[0] = src; + opt = ScriptRuntime.toString(args[data.optarg]); + } else { + opt = null; + } + Object compiled = NativeRegExp.compileRE(cx, src, opt, forceFlat); + re = new NativeRegExp(topScope, compiled); + } + data.regexp = re; + + data.global = (re.getFlags() & NativeRegExp.JSREG_GLOB) != 0; + int[] indexp = { 0 }; + Object result = null; + if (data.mode == RA_SEARCH) { + result = re.executeRegExp(cx, scope, reImpl, + str, indexp, NativeRegExp.TEST); + if (result != null && result.equals(Boolean.TRUE)) + result = new Integer(reImpl.leftContext.length); + else + result = new Integer(-1); + } else if (data.global) { + re.lastIndex = 0; + for (int count = 0; indexp[0] <= str.length(); count++) { + result = re.executeRegExp(cx, scope, reImpl, + str, indexp, NativeRegExp.TEST); + if (result == null || !result.equals(Boolean.TRUE)) + break; + if (data.mode == RA_MATCH) { + match_glob(data, cx, scope, count, reImpl); + } else { + if (data.mode != RA_REPLACE) Kit.codeBug(); + SubString lastMatch = reImpl.lastMatch; + int leftIndex = data.leftIndex; + int leftlen = lastMatch.index - leftIndex; + data.leftIndex = lastMatch.index + lastMatch.length; + replace_glob(data, cx, scope, reImpl, leftIndex, leftlen); + } + if (reImpl.lastMatch.length == 0) { + if (indexp[0] == str.length()) + break; + indexp[0]++; + } + } + } else { + result = re.executeRegExp(cx, scope, reImpl, str, indexp, + ((data.mode == RA_REPLACE) + ? NativeRegExp.TEST + : NativeRegExp.MATCH)); + } + + return result; + } + + + + public int find_split(Context cx, Scriptable scope, String target, + String separator, Scriptable reObj, + int[] ip, int[] matchlen, + boolean[] matched, String[][] parensp) + { + int i = ip[0]; + int length = target.length(); + int result; + + int version = cx.getLanguageVersion(); + NativeRegExp re = (NativeRegExp) reObj; + again: + while (true) { // imitating C label + /* JS1.2 deviated from Perl by never matching at end of string. */ + int ipsave = ip[0]; // reuse ip to save object creation + ip[0] = i; + Object ret = re.executeRegExp(cx, scope, this, target, ip, + NativeRegExp.TEST); + if (ret != Boolean.TRUE) { + // Mismatch: ensure our caller advances i past end of string. + ip[0] = ipsave; + matchlen[0] = 1; + matched[0] = false; + return length; + } + i = ip[0]; + ip[0] = ipsave; + matched[0] = true; + + SubString sep = this.lastMatch; + matchlen[0] = sep.length; + if (matchlen[0] == 0) { + /* + * Empty string match: never split on an empty + * match at the start of a find_split cycle. Same + * rule as for an empty global match in + * match_or_replace. + */ + if (i == ip[0]) { + /* + * "Bump-along" to avoid sticking at an empty + * match, but don't bump past end of string -- + * our caller must do that by adding + * sep->length to our return value. + */ + if (i == length) { + if (version == Context.VERSION_1_2) { + matchlen[0] = 1; + result = i; + } + else + result = -1; + break; + } + i++; + continue again; // imitating C goto + } + } + // PR_ASSERT((size_t)i >= sep->length); + result = i - matchlen[0]; + break; + } + int size = (parens == null) ? 0 : parens.length; + parensp[0] = new String[size]; + for (int num = 0; num < size; num++) { + SubString parsub = getParenSubString(num); + parensp[0][num] = parsub.toString(); + } + return result; + } + + /** + * Analog of REGEXP_PAREN_SUBSTRING in C jsregexp.h. + * Assumes zero-based; i.e., for $3, i==2 + */ + SubString getParenSubString(int i) + { + if (parens != null && i < parens.length) { + SubString parsub = parens[i]; + if (parsub != null) { + return parsub; + } + } + return SubString.emptySubString; + } + + /* + * Analog of match_glob() in jsstr.c + */ + private static void match_glob(GlobData mdata, Context cx, + Scriptable scope, int count, + RegExpImpl reImpl) + { + if (mdata.arrayobj == null) { + Scriptable s = ScriptableObject.getTopLevelScope(scope); + mdata.arrayobj = ScriptRuntime.newObject(cx, s, "Array", null); + } + SubString matchsub = reImpl.lastMatch; + String matchstr = matchsub.toString(); + mdata.arrayobj.put(count, mdata.arrayobj, matchstr); + } + + /* + * Analog of replace_glob() in jsstr.c + */ + private static void replace_glob(GlobData rdata, Context cx, + Scriptable scope, RegExpImpl reImpl, + int leftIndex, int leftlen) + { + int replen; + String lambdaStr; + if (rdata.lambda != null) { + // invoke lambda function with args lastMatch, $1, $2, ... $n, + // leftContext.length, whole string. + SubString[] parens = reImpl.parens; + int parenCount = (parens == null) ? 0 : parens.length; + Object[] args = new Object[parenCount + 3]; + args[0] = reImpl.lastMatch.toString(); + for (int i=0; i < parenCount; i++) { + SubString sub = parens[i]; + if (sub != null) { + args[i+1] = sub.toString(); + } else { + args[i+1] = Undefined.instance; + } + } + args[parenCount+1] = new Integer(reImpl.leftContext.length); + args[parenCount+2] = rdata.str; + // This is a hack to prevent expose of reImpl data to + // JS function which can run new regexps modifing + // regexp that are used later by the engine. + // TODO: redesign is necessary + if (reImpl != ScriptRuntime.getRegExpProxy(cx)) Kit.codeBug(); + RegExpImpl re2 = new RegExpImpl(); + re2.multiline = reImpl.multiline; + re2.input = reImpl.input; + ScriptRuntime.setRegExpProxy(cx, re2); + try { + Scriptable parent = ScriptableObject.getTopLevelScope(scope); + Object result = rdata.lambda.call(cx, parent, parent, args); + lambdaStr = ScriptRuntime.toString(result); + } finally { + ScriptRuntime.setRegExpProxy(cx, reImpl); + } + replen = lambdaStr.length(); + } else { + lambdaStr = null; + replen = rdata.repstr.length(); + if (rdata.dollar >= 0) { + int[] skip = new int[1]; + int dp = rdata.dollar; + do { + SubString sub = interpretDollar(cx, reImpl, rdata.repstr, + dp, skip); + if (sub != null) { + replen += sub.length - skip[0]; + dp += skip[0]; + } else { + ++dp; + } + dp = rdata.repstr.indexOf('$', dp); + } while (dp >= 0); + } + } + + int growth = leftlen + replen + reImpl.rightContext.length; + StringBuffer charBuf = rdata.charBuf; + if (charBuf == null) { + charBuf = new StringBuffer(growth); + rdata.charBuf = charBuf; + } else { + charBuf.ensureCapacity(rdata.charBuf.length() + growth); + } + + charBuf.append(reImpl.leftContext.charArray, leftIndex, leftlen); + if (rdata.lambda != null) { + charBuf.append(lambdaStr); + } else { + do_replace(rdata, cx, reImpl); + } + } + + private static SubString interpretDollar(Context cx, RegExpImpl res, + String da, int dp, int[] skip) + { + char dc; + int num, tmp; + + if (da.charAt(dp) != '$') Kit.codeBug(); + + /* Allow a real backslash (literal "\\") to escape "$1" etc. */ + int version = cx.getLanguageVersion(); + if (version != Context.VERSION_DEFAULT + && version <= Context.VERSION_1_4) + { + if (dp > 0 && da.charAt(dp - 1) == '\\') + return null; + } + int daL = da.length(); + if (dp + 1 >= daL) + return null; + /* Interpret all Perl match-induced dollar variables. */ + dc = da.charAt(dp + 1); + if (NativeRegExp.isDigit(dc)) { + int cp; + if (version != Context.VERSION_DEFAULT + && version <= Context.VERSION_1_4) + { + if (dc == '0') + return null; + /* Check for overflow to avoid gobbling arbitrary decimal digits. */ + num = 0; + cp = dp; + while (++cp < daL && NativeRegExp.isDigit(dc = da.charAt(cp))) + { + tmp = 10 * num + (dc - '0'); + if (tmp < num) + break; + num = tmp; + } + } + else { /* ECMA 3, 1-9 or 01-99 */ + int parenCount = (res.parens == null) ? 0 : res.parens.length; + num = dc - '0'; + if (num > parenCount) + return null; + cp = dp + 2; + if ((dp + 2) < daL) { + dc = da.charAt(dp + 2); + if (NativeRegExp.isDigit(dc)) { + tmp = 10 * num + (dc - '0'); + if (tmp <= parenCount) { + cp++; + num = tmp; + } + } + } + if (num == 0) return null; /* $0 or $00 is not valid */ + } + /* Adjust num from 1 $n-origin to 0 array-index-origin. */ + num--; + skip[0] = cp - dp; + return res.getParenSubString(num); + } + + skip[0] = 2; + switch (dc) { + case '$': + return new SubString("$"); + case '&': + return res.lastMatch; + case '+': + return res.lastParen; + case '`': + if (version == Context.VERSION_1_2) { + /* + * JS1.2 imitated the Perl4 bug where left context at each step + * in an iterative use of a global regexp started from last match, + * not from the start of the target string. But Perl4 does start + * $` at the beginning of the target string when it is used in a + * substitution, so we emulate that special case here. + */ + res.leftContext.index = 0; + res.leftContext.length = res.lastMatch.index; + } + return res.leftContext; + case '\'': + return res.rightContext; + } + return null; + } + + /** + * Analog of do_replace in jsstr.c + */ + private static void do_replace(GlobData rdata, Context cx, + RegExpImpl regExpImpl) + { + StringBuffer charBuf = rdata.charBuf; + int cp = 0; + String da = rdata.repstr; + int dp = rdata.dollar; + if (dp != -1) { + int[] skip = new int[1]; + do { + int len = dp - cp; + charBuf.append(da.substring(cp, dp)); + cp = dp; + SubString sub = interpretDollar(cx, regExpImpl, da, + dp, skip); + if (sub != null) { + len = sub.length; + if (len > 0) { + charBuf.append(sub.charArray, sub.index, len); + } + cp += skip[0]; + dp += skip[0]; + } else { + ++dp; + } + dp = da.indexOf('$', dp); + } while (dp >= 0); + } + int daL = da.length(); + if (daL > cp) { + charBuf.append(da.substring(cp, daL)); + } + } + + String input; /* input string to match (perl $_, GC root) */ + boolean multiline; /* whether input contains newlines (perl $*) */ + SubString[] parens; /* Vector of SubString; last set of parens + matched (perl $1, $2) */ + SubString lastMatch; /* last string matched (perl $&) */ + SubString lastParen; /* last paren matched (perl $+) */ + SubString leftContext; /* input to left of last match (perl $`) */ + SubString rightContext; /* input to right of last match (perl $') */ +} + + +final class GlobData +{ + int mode; /* input: return index, match object, or void */ + int optarg; /* input: index of optional flags argument */ + boolean global; /* output: whether regexp was global */ + String str; /* output: 'this' parameter object as string */ + NativeRegExp regexp;/* output: regexp parameter object private data */ + + // match-specific data + + Scriptable arrayobj; + + // replace-specific data + + Function lambda; /* replacement function object or null */ + String repstr; /* replacement string */ + int dollar = -1; /* -1 or index of first $ in repstr */ + StringBuffer charBuf; /* result characters, null initially */ + int leftIndex; /* leftContext index, always 0 for JS1.2 */ +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/SubString.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/SubString.java new file mode 100644 index 0000000..00905ca --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/regexp/SubString.java @@ -0,0 +1,75 @@ +/* -*- 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, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1997-1999 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * 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.regexp; + +class SubString { + + public SubString() + { + } + + public SubString(String str) + { + index = 0; + charArray = str.toCharArray(); + length = str.length(); + } + + public SubString(char[] source, int start, int len) + { + // there must be a better way of doing this?? + index = 0; + length = len; + charArray = new char[len]; + for (int j = 0; j < len; j++) + charArray[j] = source[start + j]; + } + + public String toString() { + return charArray == null + ? "" + : new String(charArray, index, length); + } + + static final SubString emptySubString = new SubString(); + + char[] charArray; + int index; + int length; +} + diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages.properties b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages.properties new file mode 100644 index 0000000..fd869c1 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages.properties @@ -0,0 +1,778 @@ +# +# Default JavaScript messages file. +# +# ***** 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-1999 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Norris Boyd +# Bob Jervis +# +# 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 ***** + +# This is replaced during jar assembly from property string +# and should not be translated +implementation.version = @IMPLEMENTATION.VERSION@ + +# +# To add JavaScript error messages for a particular locale, create a +# new Messages_[locale].properties file, where [locale] is the Java +# string abbreviation for that locale. For example, JavaScript +# messages for the Polish locale should be located in +# Messages_pl.properties, and messages for the Italian Swiss locale +# should be located in Messages_it_CH.properties. Message properties +# files should be accessible through the classpath under +# org.mozilla.javascript.resources +# +# See: +# java.util.ResourceBundle +# java.text.MessageFormat +# + +# SomeJavaClassWhereUsed + +# Codegen +msg.dup.parms =\ + Duplicate parameter name "{0}". + +msg.too.big.jump =\ + Program too complex: too big jump offset. + +msg.too.big.index =\ + Program too complex: internal index exceeds 64K limit. + +msg.while.compiling.fn =\ + Encountered code generation error while compiling function "{0}": {1} + +msg.while.compiling.script =\ + Encountered code generation error while compiling script: {0} + +# Context +msg.ctor.not.found =\ + Constructor for "{0}" not found. + +msg.not.ctor =\ + "{0}" is not a constructor. + +# FunctionObject +msg.varargs.ctor =\ + Method or constructor "{0}" must be static with the signature \ + "(Context cx, Object[] args, Function ctorObj, boolean inNewExpr)" \ + to define a variable arguments constructor. + +msg.varargs.fun =\ + Method "{0}" must be static with the signature \ + "(Context cx, Scriptable thisObj, Object[] args, Function funObj)" \ + to define a variable arguments function. + +msg.incompat.call =\ + Method "{0}" called on incompatible object. + +msg.bad.parms =\ + Unsupported parameter type "{0}" in method "{1}". + +msg.bad.method.return =\ + Unsupported return type "{0}" in method "{1}". + +msg.bad.ctor.return =\ + Construction of objects of type "{0}" is not supported. + +msg.no.overload =\ + Method "{0}" occurs multiple times in class "{1}". + +msg.method.not.found =\ + Method "{0}" not found in "{1}". + +# IRFactory + +msg.bad.for.in.lhs =\ + Invalid left-hand side of for..in loop. + +msg.mult.index =\ + Only one variable allowed in for..in loop. + +msg.bad.for.in.destruct =\ + Left hand side of for..in loop must be an array of length 2 to accept \ + key/value pair. + +msg.cant.convert =\ + Can''t convert to type "{0}". + +msg.bad.assign.left =\ + Invalid assignment left-hand side. + +msg.bad.decr =\ + Invalid decerement operand. + +msg.bad.incr =\ + Invalid increment operand. + +msg.bad.yield =\ + yield must be in a function. + +msg.yield.parenthesized =\ + yield expression must be parenthesized. + +# NativeGlobal +msg.cant.call.indirect =\ + Function "{0}" must be called directly, and not by way of a \ + function of another name. + +msg.eval.nonstring =\ + Calling eval() with anything other than a primitive string value will \ + simply return the value. Is this what you intended? + +msg.eval.nonstring.strict =\ + Calling eval() with anything other than a primitive string value is not \ + allowed in strict mode. + +msg.bad.destruct.op =\ + Invalid destructuring assignment operator + +# NativeCall +msg.only.from.new =\ + "{0}" may only be invoked from a "new" expression. + +msg.deprec.ctor =\ + The "{0}" constructor is deprecated. + +# NativeFunction +msg.no.function.ref.found =\ + no source found to decompile function reference {0} + +msg.arg.isnt.array =\ + second argument to Function.prototype.apply must be an array + +# NativeGlobal +msg.bad.esc.mask =\ + invalid string escape mask + +# NativeJavaClass +msg.cant.instantiate =\ + error instantiating ({0}): class {1} is interface or abstract + +msg.bad.ctor.sig =\ + Found constructor with wrong signature: \ + {0} calling {1} with signature {2} + +msg.not.java.obj =\ + Expected argument to getClass() to be a Java object. + +msg.no.java.ctor =\ + Java constructor for "{0}" with arguments "{1}" not found. + +# NativeJavaMethod +msg.method.ambiguous =\ + The choice of Java method {0}.{1} matching JavaScript argument types ({2}) is ambiguous; \ + candidate methods are: {3} + +msg.constructor.ambiguous =\ + The choice of Java constructor {0} matching JavaScript argument types ({1}) is ambiguous; \ + candidate constructors are: {2} + +# NativeJavaObject +msg.conversion.not.allowed =\ + Cannot convert {0} to {1} + +msg.no.empty.interface.conversion =\ + Cannot convert {0} to interface {1} with no methods + +msg.no.function.interface.conversion =\ + Cannot convert function {0} to interface since it contains methods with \ + different signatures + +# NativeJavaPackage +msg.not.classloader =\ + Constructor for "Packages" expects argument of type java.lang.Classloader + +# NativeRegExp +msg.bad.quant =\ + Invalid quantifier {0} + +msg.overlarge.backref =\ + Overly large back reference {0} + +msg.overlarge.min =\ + Overly large minimum {0} + +msg.overlarge.max =\ + Overly large maximum {0} + +msg.zero.quant =\ + Zero quantifier {0} + +msg.max.lt.min =\ + Maximum {0} less than minimum + +msg.unterm.quant =\ + Unterminated quantifier {0} + +msg.unterm.paren =\ + Unterminated parenthetical {0} + +msg.unterm.class =\ + Unterminated character class {0} + +msg.bad.range =\ + Invalid range in character class. + +msg.trail.backslash =\ + Trailing \\ in regular expression. + +msg.re.unmatched.right.paren =\ + unmatched ) in regular expression. + +msg.no.regexp =\ + Regular expressions are not available. + +msg.bad.backref =\ + back-reference exceeds number of capturing parentheses. + +msg.bad.regexp.compile =\ + Only one argument may be specified if the first argument to \ + RegExp.prototype.compile is a RegExp object. + +# Parser +msg.got.syntax.errors = \ + Compilation produced {0} syntax errors. + +msg.var.redecl =\ + TypeError: redeclaration of var {0}. + +msg.const.redecl =\ + TypeError: redeclaration of const {0}. + +msg.let.redecl =\ + TypeError: redeclaration of variable {0}. + +msg.parm.redecl =\ + TypeError: redeclaration of formal parameter {0}. + +msg.fn.redecl =\ + TypeError: redeclaration of function {0}. + +# NodeTransformer +msg.dup.label =\ + duplicated label + +msg.undef.label =\ + undefined label + +msg.bad.break =\ + unlabelled break must be inside loop or switch + +msg.continue.outside =\ + continue must be inside loop + +msg.continue.nonloop =\ + continue can only use labeles of iteration statements + +msg.bad.throw.eol =\ + Line terminator is not allowed between the throw keyword and throw \ + expression. + +msg.no.paren.parms =\ + missing ( before function parameters. + +msg.no.parm =\ + missing formal parameter + +msg.no.paren.after.parms =\ + missing ) after formal parameters + +msg.no.brace.body =\ + missing '{' before function body + +msg.no.brace.after.body =\ + missing } after function body + +msg.no.paren.cond =\ + missing ( before condition + +msg.no.paren.after.cond =\ + missing ) after condition + +msg.no.semi.stmt =\ + missing ; before statement + +msg.no.name.after.dot =\ + missing name after . operator + +msg.no.name.after.coloncolon =\ + missing name after :: operator + +msg.no.name.after.dotdot =\ + missing name after .. operator + +msg.no.name.after.xmlAttr =\ + missing name after .@ + +msg.no.bracket.index =\ + missing ] in index expression + +msg.no.paren.switch =\ + missing ( before switch expression + +msg.no.paren.after.switch =\ + missing ) after switch expression + +msg.no.brace.switch =\ + missing '{' before switch body + +msg.bad.switch =\ + invalid switch statement + +msg.no.colon.case =\ + missing : after case expression + +msg.double.switch.default =\ + double default label in the switch statement + +msg.no.while.do =\ + missing while after do-loop body + +msg.no.paren.for =\ + missing ( after for + +msg.no.semi.for =\ + missing ; after for-loop initializer + +msg.no.semi.for.cond =\ + missing ; after for-loop condition + +msg.in.after.for.name =\ + missing in after for + +msg.no.paren.for.ctrl =\ + missing ) after for-loop control + +msg.no.paren.with =\ + missing ( before with-statement object + +msg.no.paren.after.with =\ + missing ) after with-statement object + +msg.no.paren.after.let =\ + missing ( after let + +msg.no.paren.let =\ + missing ) after variable list + +msg.no.curly.let =\ + missing } after let statement + +msg.bad.return =\ + invalid return + +msg.no.brace.block =\ + missing } in compound statement + +msg.bad.label =\ + invalid label + +msg.bad.var =\ + missing variable name + +msg.bad.var.init =\ + invalid variable initialization + +msg.no.colon.cond =\ + missing : in conditional expression + +msg.no.paren.arg =\ + missing ) after argument list + +msg.no.bracket.arg =\ + missing ] after element list + +msg.bad.prop =\ + invalid property id + +msg.no.colon.prop =\ + missing : after property id + +msg.no.brace.prop =\ + missing } after property list + +msg.no.paren =\ + missing ) in parenthetical + +msg.reserved.id =\ + identifier is a reserved word + +msg.no.paren.catch =\ + missing ( before catch-block condition + +msg.bad.catchcond =\ + invalid catch block condition + +msg.catch.unreachable =\ + any catch clauses following an unqualified catch are unreachable + +msg.no.brace.try =\ + missing '{' before try block + +msg.no.brace.catchblock =\ + missing '{' before catch-block body + +msg.try.no.catchfinally =\ + ''try'' without ''catch'' or ''finally'' + +msg.no.return.value =\ + function {0} does not always return a value + +msg.anon.no.return.value =\ + anonymous function does not always return a value + +msg.return.inconsistent =\ + return statement is inconsistent with previous usage + +msg.generator.returns =\ + TypeError: generator function {0} returns a value + +msg.anon.generator.returns =\ + TypeError: anonymous generator function returns a value + +msg.syntax =\ + syntax error + +msg.unexpected.eof =\ + Unexpected end of file + +msg.XML.bad.form =\ + illegally formed XML syntax + +msg.XML.not.available =\ + XML runtime not available + +msg.too.deep.parser.recursion =\ + Too deep recursion while parsing + +msg.no.side.effects =\ + Code has no side effects + +msg.extra.trailing.comma =\ + Trailing comma is not legal in an ECMA-262 object initializer + +msg.equal.as.assign =\ + Test for equality (==) mistyped as assignment (=)? + +msg.var.hides.arg =\ + Variable {0} hides argument + +msg.destruct.assign.no.init =\ + Missing = in destructuring declaration + +# ScriptRuntime +msg.no.properties =\ + {0} has no properties. + +msg.invalid.iterator =\ + Invalid iterator value + +msg.iterator.primitive =\ + __iterator__ returned a primitive value + +msg.assn.create.strict =\ + Assignment to undeclared variable {0} + +msg.ref.undefined.prop =\ + Reference to undefined property "{0}" + +msg.prop.not.found =\ + Property {0} not found. + +msg.invalid.type =\ + Invalid JavaScript value of type {0} + +msg.primitive.expected =\ + Primitive type expected (had {0} instead) + +msg.namespace.expected =\ + Namespace object expected to left of :: (found {0} instead) + +msg.null.to.object =\ + Cannot convert null to an object. + +msg.undef.to.object =\ + Cannot convert undefined to an object. + +msg.cyclic.value =\ + Cyclic {0} value not allowed. + +msg.is.not.defined =\ + "{0}" is not defined. + +msg.undef.prop.read =\ + Cannot read property "{1}" from {0} + +msg.undef.prop.write =\ + Cannot set property "{1}" of {0} to "{2}" + +msg.undef.prop.delete =\ + Cannot delete property "{1}" of {0} + +msg.undef.method.call =\ + Cannot call method "{1}" of {0} + +msg.undef.with =\ + Cannot apply "with" to {0} + +msg.isnt.function =\ + {0} is not a function, it is {1}. + +msg.isnt.function.in =\ + Cannot call property {0} in object {1}. It is not a function, it is "{2}". + +msg.function.not.found =\ + Cannot find function {0}. + +msg.function.not.found.in =\ + Cannot find function {0} in object {1}. + +msg.isnt.xml.object =\ + {0} is not an xml object. + +msg.no.ref.to.get =\ + {0} is not a reference to read reference value. + +msg.no.ref.to.set =\ + {0} is not a reference to set reference value to {1}. + +msg.no.ref.from.function =\ + Function {0} can not be used as the left-hand side of assignment \ + or as an operand of ++ or -- operator. + +msg.bad.default.value =\ + Object''s getDefaultValue() method returned an object. + +msg.instanceof.not.object = \ + Can''t use instanceof on a non-object. + +msg.instanceof.bad.prototype = \ + ''prototype'' property of {0} is not an object. + +msg.bad.radix = \ + illegal radix {0}. + +# ScriptableObject +msg.default.value =\ + Cannot find default value for object. + +msg.zero.arg.ctor =\ + Cannot load class "{0}" which has no zero-parameter constructor. + +msg.ctor.multiple.parms =\ + Can''t define constructor or class {0} since more than one \ + constructor has multiple parameters. + +msg.extend.scriptable =\ + {0} must extend ScriptableObject in order to define property {1}. + +msg.bad.getter.parms =\ + In order to define a property, getter {0} must have zero parameters \ + or a single ScriptableObject parameter. + +msg.obj.getter.parms =\ + Expected static or delegated getter {0} to take a ScriptableObject parameter. + +msg.getter.static =\ + Getter and setter must both be static or neither be static. + +msg.setter.return =\ + Setter must have void return type: {0} + +msg.setter2.parms =\ + Two-parameter setter must take a ScriptableObject as its first parameter. + +msg.setter1.parms =\ + Expected single parameter setter for {0} + +msg.setter2.expected =\ + Expected static or delegated setter {0} to take two parameters. + +msg.setter.parms =\ + Expected either one or two parameters for setter. + +msg.setter.bad.type =\ + Unsupported parameter type "{0}" in setter "{1}". + +msg.add.sealed =\ + Cannot add a property to a sealed object: {0}. + +msg.remove.sealed =\ + Cannot remove a property from a sealed object: {0}. + +msg.modify.sealed =\ + Cannot modify a property of a sealed object: {0}. + +msg.modify.readonly =\ + Cannot modify readonly property: {0}. + +# TokenStream +msg.missing.exponent =\ + missing exponent + +msg.caught.nfe =\ + number format error + +msg.unterminated.string.lit =\ + unterminated string literal + +msg.unterminated.mstring.appjet =\ + unterminated multi-line string literal + +msg.unterminated.comment =\ + unterminated comment + +msg.unterminated.re.lit =\ + unterminated regular expression literal + +msg.invalid.re.flag =\ + invalid flag after regular expression + +msg.no.re.input.for =\ + no input for {0} + +msg.illegal.character =\ + illegal character + +msg.illegal.character.appjet =\ + illegal character: {0} + +msg.invalid.escape =\ + invalid Unicode escape sequence + +msg.bad.namespace =\ + not a valid default namespace statement. \ + Syntax is: default xml namespace = EXPRESSION; + +# TokensStream warnings +msg.bad.octal.literal =\ + illegal octal literal digit {0}; interpreting it as a decimal digit + +msg.reserved.keyword =\ + illegal usage of future reserved keyword {0}; interpreting it as ordinary identifier + +# LiveConnect errors +msg.java.internal.field.type =\ + Internal error: type conversion of {0} to assign to {1} on {2} failed. + +msg.java.conversion.implicit_method =\ + Can''t find converter method "{0}" on class {1}. + +msg.java.method.assign =\ + Java method "{0}" cannot be assigned to. + +msg.java.internal.private =\ + Internal error: attempt to access private/protected field "{0}". + +msg.java.no_such_method =\ + Can''t find method {0}. + +msg.script.is.not.constructor =\ + Script objects are not constructors. + +msg.nonjava.method =\ + Java method "{0}" was invoked with {1} as "this" value that can not be converted to Java type {2}. + +msg.java.member.not.found =\ + Java class "{0}" has no public instance field or method named "{1}". + +msg.java.array.index.out.of.bounds =\ + Array index {0} is out of bounds [0..{1}]. + +msg.java.array.member.not.found =\ + Java arrays have no public instance fields or methods named "{0}". + +msg.pkg.int =\ + Java package names may not be numbers. + +msg.access.prohibited =\ + Access to Java class "{0}" is prohibited. + +# ImporterTopLevel +msg.ambig.import =\ + Ambiguous import: "{0}" and and "{1}". + +msg.not.pkg =\ + Function importPackage must be called with a package; had "{0}" instead. + +msg.not.class =\ + Function importClass must be called with a class; had "{0}" instead. + +msg.not.class.not.pkg =\ + "{0}" is neither a class nor a package. + +msg.prop.defined =\ + Cannot import "{0}" since a property by that name is already defined. + +#JavaAdapter +msg.adapter.zero.args =\ + JavaAdapter requires at least one argument. + +msg.not.java.class.arg = \ +Argument {0} is not Java class: {1}. + +#JavaAdapter +msg.only.one.super = \ +Only one class may be extended by a JavaAdapter. Had {0} and {1}. + + +# Arrays +msg.arraylength.bad =\ + Inappropriate array length. + +# Arrays +msg.arraylength.too.big =\ + Array length {0} exceeds supported capacity limit. + +# URI +msg.bad.uri =\ + Malformed URI sequence. + +# Number +msg.bad.precision =\ + Precision {0} out of range. + +# NativeGenerator +msg.send.newborn =\ + Attempt to send value to newborn generator + +msg.already.exec.gen =\ + Already executing generator + +msg.StopIteration.invalid =\ + StopIteration may not be changed to an arbitrary object. + +# Interpreter +msg.yield.closing =\ + Yield from closing generator diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages_fr.properties b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages_fr.properties new file mode 100644 index 0000000..fc87c97 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/resources/Messages_fr.properties @@ -0,0 +1,329 @@ +# +# French JavaScript messages file. +# +# ***** 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 Aviva Inc. code, released +# March 5, 2004. +# +# The Initial Developer of the Original Code is +# Aviva Inc. +# Portions created by the Initial Developer are Copyright (C) 2004 +# the Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Eugene Aresteanu +# +# 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 ***** + +msg.dup.parms =\ + Le nom de param\u00E8tre "{0}" existe d\u00E9j\u00E0. +msg.too.big.jump =\ + Programme trop complexe : d\u00E9calage de saut trop important +msg.too.big.index =\ + Programme trop complexe : l''indice interne d\u00E9passe la limite de 64 ko +msg.ctor.not.found =\ + Le constructeur de "{0}" est introuvable +msg.not.ctor =\ + {0} n''est pas un constructeur +msg.varargs.ctor =\ + La m\u00E9thode ou le constructeur "{0}" doit \u00EAtre statique avec la signature "(Context cx, arguments Object[], Function ctorObj, boolean inNewExpr)" pour d\u00E9finir un constructeur d''arguments de variable. +msg.varargs.fun =\ + La m\u00E9thode "{0}" doit \u00EAtre statique avec la signature "(Context cx, Scriptable thisObj, arguments Object[], Function funObj)" pour d\u00E9finir une fonction d''arguments de variable +msg.incompat.call =\ + La m\u00E9thode "{0}" a \u00E9t\u00E9 appel\u00E9e dans un objet non compatible +msg.bad.parms =\ + Les param\u00E8tres de la m\u00E9thode sont incorrects pour "{0}" +msg.no.overload =\ + La m\u00E9thode "{0}" appara\u00EEt plusieurs fois dans la classe "{1}" +msg.method.not.found =\ + La m\u00E9thode "{0}" est introuvable dans "{1}" +msg.bad.for.in.lhs =\ + La partie gauche de la boucle for..in est incorrecte +msg.bad.lhs.assign =\ + La partie gauche de l''affectation est incorrecte +msg.mult.index =\ + Une seule variable est autoris\u00E9e dans la boucle for..in +msg.cant.convert =\ + La conversion en type "{0}" est impossible +msg.cant.call.indirect =\ + La fonction "{0}" doit \u00EAtre appel\u00E9e directement et non par l''interm\u00E9diaire d''une fonction portant un autre nom +msg.eval.nonstring =\ + Si vous appelez la fonction eval() avec une valeur qui n''appartient pas \u00E0 une cha\u00EEne primitive, c''est la valeur en question qui est renvoy\u00E9e. \u00E9tait-ce votre intention ? +msg.only.from.new =\ + {0} ne peut \u00EAtre appel\u00E9e qu''\u00E0 partir d''une "nouvelle" expression. +msg.deprec.ctor =\ + Le constructeur "{0}" est d\u00E9conseill\u00E9 +msg.no.function.ref.found =\ + aucune source n''a \u00E9t\u00E9 trouv\u00E9e pour d\u00E9compiler la r\u00E9f\u00E9rence de fonction {0} +msg.arg.isnt.array =\ + le second argument de la m\u00E9thode Function.prototype.apply doit \u00EAtre un tableau +msg.bad.esc.mask =\ + le masque d''\u00E9chappement de cha\u00EEne est incorrect +msg.cant.instantiate =\ + erreur lors de l''instanciation ({0}) : la classe {1} est une classe interface ou abstract +msg.bad.ctor.sig =\ + Un constructeur avec une signature incorrecte a \u00E9t\u00E9 d\u00E9tect\u00E9 : {0} qui appelle {1} avec la signature {2} +msg.not.java.obj =\ + L''argument attendu pour la fonction getClass() doit \u00EAtre un objet Java +msg.no.java.ctor =\ + Le constructeur Java de "{0}" avec les arguments "{1}" est introuvable +msg.method.ambiguous =\ + Le choix de la m\u00E9thode Java {0}.{1} correspondant aux types d''argument JavaScript ({2}) est ambigu. Les m\u00E9thodes propos\u00E9es sont les suivantes : {3} +msg.constructor.ambiguous =\ + Le choix du constructeur Java {0} correspondant aux types d''argument JavaScript ({1}) est ambigu. Les constructeurs propos\u00E9s sont les suivants : {2} +msg.conversion.not.allowed =\ + Impossible de convertir {0} en {1} +msg.not.classloader =\ + Le constructeur de "Packages" attend un argument de type java.lang.Classloader +msg.bad.quant =\ + Le quantificateur {0} est incorrect +msg.overlarge.max =\ + Le maximum {0} est trop important +msg.zero.quant =\ + Le quantificateur {0} est nul +msg.max.lt.min =\ + Le maximum {0} est inf\u00E9rieur au minimum +msg.unterm.quant =\ + Le quantificateur {0} n''a pas de limite +msg.unterm.paren =\ + Les parenth\u00E8ses {0} n''ont pas de limite +msg.unterm.class =\ + La classe de caract\u00E8res {0} n''a pas de limite +msg.bad.range =\ + La classe de caract\u00E8res contient une plage de valeurs incorrecte +msg.trail.backslash =\ + \\ au d\u00E9but d''une expression r\u00E9guli\u00E8re +msg.no.regexp =\ + Les expressions r\u00E9guli\u00E8res ne sont pas disponibles +msg.bad.backref =\ + la r\u00E9f\u00E9rence ant\u00E9rieure d\u00E9passe le nombre de parenth\u00E8ses de capture +msg.dup.label =\ + Le libell\u00E9 {0} existe d\u00E9j\u00E0 +msg.undef.label =\ + Le libell\u00E9 {0} n''est pas d\u00E9fini +msg.bad.break =\ + Le saut non libell\u00E9 doit se trouver dans la boucle ou dans l''aiguillage +msg.continue.outside =\ + continue doit se trouver dans la boucle +msg.continue.nonloop =\ + Il n''est possible de continuer que dans l''instruction d''it\u00E9ration libell\u00E9e +msg.fn.redecl =\ + La fonction "{0}" a \u00E9t\u00E9 de nouveau d\u00E9clar\u00E9e. La d\u00E9finition pr\u00E9c\u00E9dente sera ignor\u00E9e +msg.no.paren.parms =\ + il manque ''('' avant les param\u00E8tres de la fonction +msg.no.parm =\ + il manque un param\u00E8tre de forme +msg.no.paren.after.parms =\ + il manque '')'' apr\u00E8s les param\u00E8tres de forme +msg.no.brace.body =\ + il manque '{' avant le corps d''une fonction +msg.no.brace.after.body =\ + il manque ''}'' apr\u00E8s le corps d''une fonction +msg.no.paren.cond =\ + il manque ''('' avant une condition +msg.no.paren.after.cond =\ + il manque '')'' apr\u00E8s une condition +msg.no.semi.stmt =\ + il manque '';'' avant une instruction +msg.no.name.after.dot =\ + il manque un nom apr\u00E8s un op\u00E9rateur ''.'' +msg.no.bracket.index =\ + il manque '']'' dans l''expression de l''indice +msg.no.paren.switch =\ + il manque ''('' avant l''expression d''un aiguillage +msg.no.paren.after.switch =\ + il manque '')'' apr\u00E8s l''expression d''un aiguillage +msg.no.brace.switch =\ + il manque '{' avant le corps d''un aiguillage +msg.bad.switch =\ + l''instruction d''aiguillage est incorrecte +msg.no.colon.case =\ + il manque '':'' apr\u00E8s l''expression d''un cas +msg.no.while.do =\ + il manque ''while'' apr\u00E8s le corps d''une boucle do-loop +msg.no.paren.for =\ + il manque ''('' apr\u00E8s for +msg.no.semi.for =\ + Il manque '';'' apr\u00E8s l''initialiseur for-loop +msg.no.semi.for.cond =\ + il manque '';'' apr\u00E8s la condition for-loop +msg.no.paren.for.ctrl =\ + il manque '')'' apr\u00E8s le contrôle for-loop +msg.no.paren.with =\ + il manque ''('' avant un objet with-statement +msg.no.paren.after.with =\ + il manque '')'' apr\u00E8s un objet with-statement +msg.bad.return =\ + la valeur renvoy\u00E9e est incorrecte +msg.no.brace.block =\ + il manque ''}'' dans une instruction compos\u00E9e +msg.bad.label =\ + le libell\u00E9 est incorrect +msg.bad.var =\ + il manque un nom de variable +msg.bad.var.init =\ + l''initialisation de la variable est incorrecte +msg.no.colon.cond =\ + il manque '':'' dans une expression conditionnelle +msg.no.paren.arg =\ + il manque '')'' apr\u00E8s une liste d''arguments +msg.no.bracket.arg =\ + il manque '']'' apr\u00E8s une liste d''\u00E9l\u00E9ments +msg.bad.prop =\ + l''identifiant de propri\u00E9t\u00E9 est incorrect +msg.no.colon.prop =\ + il manque '':'' apr\u00E8s un identifiant de propri\u00E9t\u00E9 +msg.no.brace.prop =\ + il manque ''}'' apr\u00E8s une liste de propri\u00E9t\u00E9s +msg.no.paren =\ + il manque '')'' dans des parenth\u00E8ses +msg.reserved.id =\ + l''identifiant est un mot r\u00E9serv\u00E9 +msg.no.paren.catch =\ + il manque ''('' avant une condition catch-block +msg.bad.catchcond =\ + la condition catch-block est incorrecte +msg.catch.unreachable =\ + aucune clause catch suivant une interception non qualifi\u00E9e ne peut \u00EAtre atteinte +msg.no.brace.catchblock =\ + il manque '{' avant le corps catch-block +msg.try.no.catchfinally =\ + ''try'' a \u00E9t\u00E9 d\u00E9tect\u00E9 sans ''catch'' ni ''finally'' +msg.syntax =\ + erreur de syntaxe +msg.assn.create =\ + Une variable va \u00EAtre cr\u00E9\u00E9e en raison de l''affectation \u00E0 un ''{0}'' non d\u00E9fini. Ajoutez une instruction de variable \u00E0 la port\u00E9e sup\u00E9rieure pour que cet avertissement ne soit plus affich\u00E9 +msg.prop.not.found =\ + La propri\u00E9t\u00E9 est introuvable +msg.invalid.type =\ + Valeur JavaScript de type {0} incorrecte +msg.primitive.expected =\ + Un type primitif \u00E9tait attendu (et non {0}) +msg.null.to.object =\ + Il est impossible de convertir la valeur null en objet +msg.undef.to.object =\ + Il est impossible de convertir une valeur non d\u00E9finie en objet +msg.cyclic.value =\ + La valeur cyclique {0} n''est pas autoris\u00E9e +msg.is.not.defined =\ + "{0}" n''est pas d\u00E9fini +msg.isnt.function =\ + {0} n''est pas une fonction, est un {1} +msg.bad.default.value =\ + La m\u00E9thode getDefaultValue() de l''objet a renvoy\u00E9 un objet +msg.instanceof.not.object =\ + Il est impossible d''utiliser une instance d''un \u00E9l\u00E9ment autre qu''un objet +msg.instanceof.bad.prototype =\ + La propri\u00E9t\u00E9 ''prototype'' de {0} n''est pas un objet +msg.bad.radix =\ + la base {0} n''est pas autoris\u00E9e +msg.default.value =\ + La valeur par d\u00E9faut de l''objet est introuvable +msg.zero.arg.ctor =\ + Il est impossible de charger la classe "{0}", qui ne poss\u00E8de pas de constructeur de param\u00E8tre z\u00E9ro +msg.multiple.ctors =\ + Les m\u00E9thodes {0} et {1} ont \u00E9t\u00E9 d\u00E9tect\u00E9es alors qu''il est impossible d''utiliser plusieurs m\u00E9thodes constructor +msg.ctor.multiple.parms =\ + Il est impossible de d\u00E9finir le constructeur ou la classe {0} car plusieurs constructeurs poss\u00E8dent plusieurs param\u00E8tres +msg.extend.scriptable =\ + {0} doit \u00E9tendre ScriptableObject afin de d\u00E9finir la propri\u00E9t\u00E9 {1} +msg.bad.getter.parms =\ + Pour d\u00E9finir une propri\u00E9t\u00E9, la m\u00E9thode d''obtention {0} doit avoir des param\u00E8tres z\u00E9ro ou un seul param\u00E8tre ScriptableObject +msg.obj.getter.parms =\ + La m\u00E9thode d''obtention statique ou d\u00E9l\u00E9gu\u00E9e {0} doit utiliser un param\u00E8tre ScriptableObject +msg.getter.static =\ + La m\u00E9thode d''obtention et la m\u00E9thode de d\u00E9finition doivent toutes deux avoir le m\u00EAme \u00E9tat (statique ou non) +msg.setter2.parms =\ + La m\u00E9thode de d\u00E9finition \u00E0 deux param\u00E8tres doit utiliser un param\u00E8tre ScriptableObject comme premier param\u00E8tre +msg.setter1.parms =\ + Une m\u00E9thode d''obtention \u00E0 param\u00E8tre unique est attendue pour {0} +msg.setter2.expected =\ + La m\u00E9thode de d\u00E9finition statique ou d\u00E9l\u00E9gu\u00E9e {0} doit utiliser deux param\u00E8tres +msg.setter.parms =\ + Un ou deux param\u00E8tres sont attendus pour la m\u00E9thode de d\u00E9finition +msg.add.sealed =\ + Il est impossible d''ajouter une propri\u00E9t\u00E9 \u00E0 un objet ferm\u00E9 +msg.remove.sealed =\ + Il est impossible de supprimer une propri\u00E9t\u00E9 d''un objet ferm\u00E9 +msg.token.replaces.pushback =\ + le jeton de non-obtention {0} remplace le jeton de renvoi {1} +msg.missing.exponent =\ + il manque un exposant +msg.caught.nfe =\ + erreur de format de nombre : {0} +msg.unterminated.string.lit =\ + le litt\u00E9ral de la cha\u00EEne n''a pas de limite +msg.unterminated.comment =\ + le commentaire n''a pas de limite +msg.unterminated.re.lit =\ + le litt\u00E9ral de l''expression r\u00E9guli\u00E8re n''a pas de limite +msg.invalid.re.flag =\ + une expression r\u00E9guli\u00E8re est suivie d''un indicateur incorrect +msg.no.re.input.for =\ + il n''y a pas d''entr\u00E9e pour {0} +msg.illegal.character =\ + caract\u00E8re non autoris\u00E9 +msg.invalid.escape =\ + la s\u00E9quence d''\u00E9chappement Unicode est incorrecte +msg.bad.octal.literal =\ + le chiffre octal du litt\u00E9ral, {0}, n''est pas autoris\u00E9 et sera interpr\u00E9t\u00E9 comme un chiffre d\u00E9cimal +msg.reserved.keyword =\ + l''utilisation du futur mot-cl\u00E9 r\u00E9serv\u00E9 {0} n''est pas autoris\u00E9e et celui-ci sera interpr\u00E9t\u00E9 comme un identifiant ordinaire +msg.undefined =\ + La valeur non d\u00E9finie ne poss\u00E8de pas de propri\u00E9t\u00E9 +msg.java.internal.field.type =\ + Erreur interne : la conversion de type de {0} afin d''affecter {1} \u00E0 {2} a \u00E9chou\u00E9 +msg.java.conversion.implicit_method =\ + La m\u00E9thode de conversion "{0}" est introuvable dans la classe {1} +sg.java.method.assign =\ + La m\u00E9thode Java "{0}" ne peut pas \u00EAtre affect\u00E9e \u00E0 +msg.java.internal.private =\ + Erreur interne : une tentative d''acc\u00E9der \u00E0 un champ "{0}" priv\u00E9/prot\u00E9g\u00E9 a \u00E9t\u00E9 d\u00E9tect\u00E9e +msg.java.no_such_method =\ + La m\u00E9thode ''{0}'' est introuvable +msg.script.is.not.constructor =\ + Les objets Script ne sont pas des constructeurs +msg.nonjava.method =\ + La m\u00E9thode Java "{0}" a \u00E9t\u00E9 appel\u00E9e avec une valeur ''this'' qui n''est pas un objet Java +msg.java.member.not.found =\ + La classe Java "{0}" ne poss\u00E8de aucun champ ou aucune m\u00E9thode d''instance publique appel\u00E9 "{1}" +msg.java.array.index.out.of.bounds =\ + Array index {0} is out of bounds [0..{1}]. +msg.pkg.int =\ + Les noms de package Java ne peuvent pas \u00EAtre des nombres +msg.ambig.import =\ + Importation ambigu\u00EB : "{0}" et "{1}" +msg.not.pkg =\ + La fonction importPackage doit \u00EAtre appel\u00E9e avec un package et non avec "{0}" +msg.not.class =\ + La fonction importClass doit \u00EAtre appel\u00E9e avec une classe et non avec "{0}" +msg.prop.defined =\ + Il est impossible d''importer "{0}" car une propri\u00E9t\u00E9 portant le m\u00EAme nom a d\u00E9j\u00E0 \u00E9t\u00E9 d\u00E9finie +sg.arraylength.bad =\ + La longueur du tableau n''est pas appropri\u00E9e +msg.bad.uri =\ + La s\u00E9quence URI n''est pas form\u00E9e correctement +msg.bad.precision =\ + La pr\u00E9cision {0} ne se trouve pas dans la plage de valeurs diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableInputStream.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableInputStream.java new file mode 100644 index 0000000..476ff69 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableInputStream.java @@ -0,0 +1,112 @@ +/* -*- 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 serialization code, released + * Sept. 25, 2001. + * + * The Initial Developer of the Original Code is + * Norris Boyd. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Attila Szegedi + * + * 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 ***** */ + +// API class + +package org.mozilla.javascript.serialize; + +import java.io.*; + +import org.mozilla.javascript.*; + +/** + * Class ScriptableInputStream is used to read in a JavaScript + * object or function previously serialized with a ScriptableOutputStream. + * References to names in the exclusion list + * replaced with references to the top-level scope specified during + * creation of the ScriptableInputStream. + * + * @author Norris Boyd + */ + +public class ScriptableInputStream extends ObjectInputStream { + + /** + * Create a ScriptableInputStream. + * @param in the InputStream to read from. + * @param scope the top-level scope to create the object in. + */ + public ScriptableInputStream(InputStream in, Scriptable scope) + throws IOException + { + super(in); + this.scope = scope; + enableResolveObject(true); + Context cx = Context.getCurrentContext(); + if (cx != null) { + this.classLoader = cx.getApplicationClassLoader(); + } + } + + protected Class resolveClass(ObjectStreamClass desc) + throws IOException, ClassNotFoundException + { + String name = desc.getName(); + if (classLoader != null) { + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException ex) { + // fall through to default loading + } + } + return super.resolveClass(desc); + } + + protected Object resolveObject(Object obj) + throws IOException + { + if (obj instanceof ScriptableOutputStream.PendingLookup) { + String name = ((ScriptableOutputStream.PendingLookup)obj).getName(); + obj = ScriptableOutputStream.lookupQualifiedName(scope, name); + if (obj == Scriptable.NOT_FOUND) { + throw new IOException("Object " + name + " not found upon " + + "deserialization."); + } + }else if (obj instanceof UniqueTag) { + obj = ((UniqueTag)obj).readResolve(); + }else if (obj instanceof Undefined) { + obj = ((Undefined)obj).readResolve(); + } + return obj; + } + + private Scriptable scope; + private ClassLoader classLoader; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java new file mode 100644 index 0000000..5ba0d74 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/serialize/ScriptableOutputStream.java @@ -0,0 +1,207 @@ +/* -*- 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 serialization code, released + * Sept. 25, 2001. + * + * The Initial Developer of the Original Code is + * Norris Boyd. + * Portions created by the Initial Developer are Copyright (C) 2001 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Attila Szegedi + * + * 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.serialize; + +import java.util.Hashtable; +import java.util.StringTokenizer; +import java.io.*; + +import org.mozilla.javascript.*; + +/** + * Class ScriptableOutputStream is an ObjectOutputStream used + * to serialize JavaScript objects and functions. Note that + * compiled functions currently cannot be serialized, only + * interpreted functions. The top-level scope containing the + * object is not written out, but is instead replaced with + * another top-level object when the ScriptableInputStream + * reads in this object. Also, object corresponding to names + * added to the exclude list are not written out but instead + * are looked up during deserialization. This approach avoids + * the creation of duplicate copies of standard objects + * during deserialization. + * + * @author Norris Boyd + */ + +// API class + +public class ScriptableOutputStream extends ObjectOutputStream { + + /** + * ScriptableOutputStream constructor. + * Creates a ScriptableOutputStream for use in serializing + * JavaScript objects. Calls excludeStandardObjectNames. + * + * @param out the OutputStream to write to. + * @param scope the scope containing the object. + */ + public ScriptableOutputStream(OutputStream out, Scriptable scope) + throws IOException + { + super(out); + this.scope = scope; + table = new Hashtable(31); + table.put(scope, ""); + enableReplaceObject(true); + excludeStandardObjectNames(); + } + + /** + * Adds a qualified name to the list of object to be excluded from + * serialization. Names excluded from serialization are looked up + * in the new scope and replaced upon deserialization. + * @param name a fully qualified name (of the form "a.b.c", where + * "a" must be a property of the top-level object). The object + * need not exist, in which case the name is ignored. + * @throws IllegalArgumentException if the object is not a + * {@link Scriptable}. + */ + public void addOptionalExcludedName(String name) { + Object obj = lookupQualifiedName(scope, name); + if(obj != null && obj != UniqueTag.NOT_FOUND) { + if (!(obj instanceof Scriptable)) { + throw new IllegalArgumentException( + "Object for excluded name " + name + + " is not a Scriptable, it is " + + obj.getClass().getName()); + } + table.put(obj, name); + } + } + + /** + * Adds a qualified name to the list of object to be excluded from + * serialization. Names excluded from serialization are looked up + * in the new scope and replaced upon deserialization. + * @param name a fully qualified name (of the form "a.b.c", where + * "a" must be a property of the top-level object) + * @throws IllegalArgumentException if the object is not found or is not + * a {@link Scriptable}. + */ + public void addExcludedName(String name) { + Object obj = lookupQualifiedName(scope, name); + if (!(obj instanceof Scriptable)) { + throw new IllegalArgumentException("Object for excluded name " + + name + " not found."); + } + table.put(obj, name); + } + + /** + * Returns true if the name is excluded from serialization. + */ + public boolean hasExcludedName(String name) { + return table.get(name) != null; + } + + /** + * Removes a name from the list of names to exclude. + */ + public void removeExcludedName(String name) { + table.remove(name); + } + + /** + * Adds the names of the standard objects and their + * prototypes to the list of excluded names. + */ + public void excludeStandardObjectNames() { + String[] names = { "Object", "Object.prototype", + "Function", "Function.prototype", + "String", "String.prototype", + "Math", // no Math.prototype + "Array", "Array.prototype", + "Error", "Error.prototype", + "Number", "Number.prototype", + "Date", "Date.prototype", + "RegExp", "RegExp.prototype", + "Script", "Script.prototype", + "Continuation", "Continuation.prototype", + }; + for (int i=0; i < names.length; i++) { + addExcludedName(names[i]); + } + + String[] optionalNames = { + "XML", "XML.prototype", + "XMLList", "XMLList.prototype", + }; + for (int i=0; i < optionalNames.length; i++) { + addOptionalExcludedName(optionalNames[i]); + } + } + + static Object lookupQualifiedName(Scriptable scope, + String qualifiedName) + { + StringTokenizer st = new StringTokenizer(qualifiedName, "."); + Object result = scope; + while (st.hasMoreTokens()) { + String s = st.nextToken(); + result = ScriptableObject.getProperty((Scriptable)result, s); + if (result == null || !(result instanceof Scriptable)) + break; + } + return result; + } + + static class PendingLookup implements Serializable + { + static final long serialVersionUID = -2692990309789917727L; + + PendingLookup(String name) { this.name = name; } + + String getName() { return name; } + + private String name; + } + + protected Object replaceObject(Object obj) throws IOException + { + String name = (String) table.get(obj); + if (name == null) + return obj; + return new PendingLookup(name); + } + + private Scriptable scope; + private Hashtable table; +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLLib.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLLib.java new file mode 100644 index 0000000..da57ddf --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLLib.java @@ -0,0 +1,132 @@ +/* -*- 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): + * Igor Bukanov + * + * 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.xml; + +import org.mozilla.javascript.*; + +public abstract class XMLLib +{ + private static final Object XML_LIB_KEY = new Object(); + + /** + An object which specifies an XMLLib implementation to be used at runtime. + + This interface should be considered experimental. It may be better + (and certainly more flexible) to write an interface that returns an + XMLLib object rather than a class name, for example. But that would + cause many more ripple effects in the code, all the way back to + {@link ScriptRuntime}. + */ + public static abstract class Factory { + public static Factory create(final String className) { + return new Factory() { + public String getImplementationClassName() { + return className; + } + }; + } + + public abstract String getImplementationClassName(); + } + + public static XMLLib extractFromScopeOrNull(Scriptable scope) + { + ScriptableObject so = ScriptRuntime.getLibraryScopeOrNull(scope); + if (so == null) { + // If library is not yet initialized, return null + return null; + } + + // Ensure lazily initialization of real XML library instance + // which is done on first access to XML property + ScriptableObject.getProperty(so, "XML"); + + return (XMLLib)so.getAssociatedValue(XML_LIB_KEY); + } + + public static XMLLib extractFromScope(Scriptable scope) + { + XMLLib lib = extractFromScopeOrNull(scope); + if (lib != null) { + return lib; + } + String msg = ScriptRuntime.getMessage0("msg.XML.not.available"); + throw Context.reportRuntimeError(msg); + } + + protected final XMLLib bindToScope(Scriptable scope) + { + ScriptableObject so = ScriptRuntime.getLibraryScopeOrNull(scope); + if (so == null) { + // standard library should be initialized at this point + throw new IllegalStateException(); + } + return (XMLLib)so.associateValue(XML_LIB_KEY, this); + } + + public abstract boolean isXMLName(Context cx, Object name); + + public abstract Ref nameRef(Context cx, Object name, + Scriptable scope, int memberTypeFlags); + + public abstract Ref nameRef(Context cx, Object namespace, Object name, + Scriptable scope, int memberTypeFlags); + + /** + * Escapes the reserved characters in a value of an attribute. + * + * @param value Unescaped text + * @return The escaped text + */ + public abstract String escapeAttributeValue(Object value); + + /** + * Escapes the reserved characters in a value of a text node. + * + * @param value Unescaped text + * @return The escaped text + */ + public abstract String escapeTextValue(Object value); + + + /** + * Construct namespace for default xml statement. + */ + public abstract Object toDefaultXmlNamespace(Context cx, Object uriValue); +} diff --git a/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLObject.java b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLObject.java new file mode 100644 index 0000000..5033564 --- /dev/null +++ b/trunk/trunk/infrastructure/rhino1_7R1/src/org/mozilla/javascript/xml/XMLObject.java @@ -0,0 +1,128 @@ +/* -*- 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): + * Igor Bukanov + * Ethan Hugg + * Terry Lucas + * Milen Nankov + * + * 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.xml; + +import org.mozilla.javascript.*; + +/** + * This Interface describes what all XML objects (XML, XMLList) should have in common. + * + */ +public abstract class XMLObject extends IdScriptableObject +{ + public XMLObject() + { + } + + public XMLObject(Scriptable scope, Scriptable prototype) + { + super(scope, prototype); + } + + /** + * Implementation of ECMAScript [[Has]]. + */ + public abstract boolean ecmaHas(Context cx, Object id); + + /** + * Implementation of ECMAScript [[Get]]. + */ + public abstract Object ecmaGet(Context cx, Object id); + + /** + * Implementation of ECMAScript [[Put]]. + */ + public abstract void ecmaPut(Context cx, Object id, Object value); + + /** + * Implementation of ECMAScript [[Delete]]. + */ + public abstract boolean ecmaDelete(Context cx, Object id); + + /** + * Return an additional object to look for methods that runtime should + * consider during method search. Return null if no such object available. + */ + public abstract Scriptable getExtraMethodSource(Context cx); + + /** + * Generic reference to implement x.@y, x..y etc. + */ + public abstract Ref memberRef(Context cx, Object elem, + int memberTypeFlags); + + /** + * Generic reference to implement x::ns, x.@ns::y, x..@ns::y etc. + */ + public abstract Ref memberRef(Context cx, Object namespace, Object elem, + int memberTypeFlags); + + /** + * Wrap this object into NativeWith to implement the with statement. + */ + public abstract NativeWith enterWith(Scriptable scope); + + /** + * Wrap this object into NativeWith to implement the .() query. + */ + public abstract NativeWith enterDotQuery(Scriptable scope); + + /** + * Custom <tt>+</tt> operator. + * Should return {@link Scriptable#NOT_FOUND} if this object does not have + * custom addition operator for the given value, + * or the result of the addition operation. + * <p> + * The default implementation returns {@link Scriptable#NOT_FOUND} + * to indicate no custom addition operation. + * + * @param cx the Context object associated with the current thread. + * @param thisIsLeft if true, the object should calculate this + value + * if false, the object should calculate value + this. + * @param value the second argument for addition operation. + */ + public Object addValues(Context cx, boolean thisIsLeft, Object value) + { + return Scriptable.NOT_FOUND; + } + +} |