/** * Sort the array contents between the given indices using a Javascript * comparator function, * * @param left lower interval border (inclusive) * @param right higher interval border (exclusive) */ private void sort(int left, int right, JsFunction compare, JsObject ctx, JsArray stack, int sp) { if (right > left + 1) { int pivotIndex = left + JsSystem.random.Next(right - left); swap(pivotIndex, right - 1); int storeIndex = left; for (int i = left; i < right - 1; i++) { stack.setObject(sp, ctx); stack.setObject(sp + 1, compare); copy(i, stack, sp + 2); copy(right - 1, stack, sp + 3); compare.eval(stack, sp, 2); if (stack.getNumber(sp) <= 0) { swap(i, storeIndex++); } } swap(storeIndex, right - 1); sort(left, storeIndex, compare, ctx, stack, sp); sort(storeIndex, right, compare, ctx, stack, sp); } }
/** * Sets the given property to the given value, taking the prototype chain, * scope chain, and setters into account. * * @param prop property name * @param value value to set * @return this (for chained calls) */ public virtual void setObject(String key, Object v) { Object old = getRawInPrototypeChain(key); if (old is JsFunction && ((JsFunction)old).getParameterCount() == -1) { JsFunction nat = (JsFunction)old; JsArray stack = new JsArray(); stack.setObject(0, v); evalNative(nat.index + 1, stack, 0, 0); return; } else if (old == null && scopeChain != null) { scopeChain.setObject(key, v); } else { if (data == null) { data = new Hashtable(); } data[key] = (v == null ? UNDEFINED_PLACEHOLDER : v); } }
/** * Creates a new function from the given function literal and context. */ public JsFunction(JsFunction literal, JsObject context) : base(literal.__proto__) { this.byteCode = literal.byteCode; this.context = context; this.functionLiterals = literal.functionLiterals; this.localNames = literal.localNames; this.numberLiterals = literal.numberLiterals; this.expectedParameterCount = literal.expectedParameterCount; this.prototype = literal.prototype; this.stringLiterals = literal.stringLiterals; this.varCount = literal.varCount; this.factory = JsSystem.getInstance(); this.factoryTypeId = JsSystem.FACTORY_ID_OBJECT; this.lineNumbers = literal.lineNumbers; }
/** * Parses the given stream and runs the main function * @throws IOException */ public static Object exec(DataInputStream dis, JsObject context) { //TODO check magic for (int i = 0; i < 8; i++) { dis.read(); } JsFunction main = new JsFunction(new JsFunction(dis, null), context); JsArray stack = new JsArray(); stack.setObject(0, context); stack.setObject(1, context); stack.setObject(2, main); main.eval(stack, 1, 0); return(stack.getObject(3)); }
/** * Returns the given property, taking native getters into account. * * @param prop Name of the property * @return stored value or null */ public virtual Object getObject(String prop) { Object v = getRawInPrototypeChain(prop); if (v is JsFunction) { JsFunction nat = (JsFunction)v; if (nat.getParameterCount() == -1) { JsArray stack = new JsArray(); evalNative(nat.index, stack, 0, 0); return(stack.getObject(0)); } } else if (v == null && scopeChain != null) { v = scopeChain.getObject(prop); } return(v); }
public static Object eval(String input, JsObject context) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); compile(input, baos); sbyte[] code = baos.toByteArray(); if (false) { Console.WriteLine("code == "); for (int k = 0; k < code.Length; ++k) { Console.WriteLine("code[" + k + "] == " + code[k]); } } if (Config.DEBUG_DISSASSEMBLY) { new Disassembler(new DataInputStream(new ByteArrayInputStream(code))).dump(); } return(JsFunction.exec(new DataInputStream(new ByteArrayInputStream(code)), context)); }
/** * Execute java member implementation. Parameters for functions start at * stack[sp+2]. Function and getter results are returned at stack[sp+0]. * The assignement value for a setter is stored at stack[sp+0]. */ public virtual void evalNative(int index, JsArray stack, int sp, int parCount) { Object obj; switch (index) { // object methods case ID_NOOP: break; case ID_INIT_OBJECT: obj = stack.getObject(sp + 2); if (isConstruction(stack, sp)) { if (obj is Boolean || obj is Double || obj is String) { value = obj; } else if (obj is JsObject) { stack.setObject(sp - 1, obj); } // otherwise, don't do anything -- regular constructor call } else { if (obj == null || obj == JsSystem.JS_NULL) { stack.setObject(sp, new JsObject(OBJECT_PROTOTYPE)); } else { stack.setObject(sp, JsSystem.toJsObject(obj)); } } break; case ID_TO_STRING: case ID_TO_LOCALE_STRING: stack.setObject(sp, JsSystem.toString(stack.getObject(sp))); break; case ID_HAS_OWN_PROPERTY: stack.setBoolean(sp, data != null && data[stack.getString(sp + 2)] != null); break; case ID_IS_PROTOTYPE_OF: obj = stack.getObject(sp + 2); stack.setBoolean(sp, false); while (obj is JsObject) { if (obj == this) { stack.setBoolean(sp, true); break; } } break; case ID_PROPERTY_IS_ENUMERABLE: obj = getRawInPrototypeChain(stack.getString(sp + 2)); stack.setBoolean(sp, obj != null && !(obj is JsFunction)); break; case ID_VALUE_OF: stack.setObject(sp, value == null ? this : value); break; // Number methods case ID_INIT_NUMBER: if (isConstruction(stack, sp)) { value = (Double)(stack.getNumber(sp + 2)); } else { stack.setNumber(sp, stack.getNumber(sp + 2)); } break; // Boolean methods case ID_INIT_BOOLEAN: if (isConstruction(stack, sp)) { value = stack.getBoolean(sp + 2) ? true : false; } else { stack.setObject(sp, stack.getBoolean(sp + 2) ? true : false); } break; case ID_INIT_STRING: if (isConstruction(stack, sp)) { value = stack.getString(sp + 2); } else { stack.setObject(sp, parCount == 0 ? "" : stack.getString(sp + 2)); } break; // initializers that can be used as functions need to be covered in Object case ID_INIT_ARRAY: JsArray array = (isConstruction(stack, sp) ? (JsArray)this : new JsArray()); if (parCount == 1 && stack.isNumber(sp + 2)) { array.setSize(stack.getInt(sp + 2)); } else { for (int i2 = 0; i2 < parCount; i2++) { stack.copy(sp + i2 + 2, array, i2); } } stack.setObject(sp, array); break; case ID_INIT_ERROR: if (isConstruction(stack, sp)) { setObject("message", stack.getString(sp + 2)); } else { stack.setObject(sp, new JsError(stack.getJsObject(sp), stack.getString(sp + 2))); } break; case ID_INIT_FUNCTION: // Note: this will exchange the "this" object at sp-1 if it is used as constructor bool construction = isConstruction(stack, sp); StringBuilder buf = new StringBuilder("(function("); for (int i3 = 0; i3 < parCount - 1; i3++) { if (i3 != 0) { buf.Append(','); } buf.Append(stack.getString(sp + 2 + i3)); } buf.Append("){"); if (parCount != 0) { buf.Append(stack.getString(sp + 2 + parCount - 1)); } buf.Append("});"); Console.WriteLine("eval: " + buf); JsObject global = (JsObject)stack.getObject(0); JsFunction eval = (JsFunction)global.getObject("eval"); stack.setObject(sp, global); // global stack.setObject(sp + 1, eval); stack.setObject(sp + 2, buf.ToString()); eval.eval(stack, sp, 1); if (construction) { stack.copy(sp, stack, sp - 1); } break; case ID_INIT_DATE: // reset to defaults if (isConstruction(stack, sp)) { JsDate d_2 = (JsDate)this; if (parCount == 1) { d_2.time.setTime(new DateTime((long)stack.getNumber(sp + 2))); } else if (parCount > 1) { d_2.time.setTime(new DateTime()); int year = stack.getInt(sp + 2); if (year >= 0 && year <= 99) { year += 1900; } d_2.setDate(false, year, stack.getNumber(sp + 3), stack.getNumber(sp + 4)); d_2.setTime(false, stack.getNumber(sp + 5), stack.getNumber(sp + 6), stack.getNumber(sp + 7), stack.getNumber(sp + 8)); } } else { stack.setObject(sp, new JsDate(JsDate.DATE_PROTOTYPE).toString(true, true, true)); } break; // global properties case ID_PRINT: Console.WriteLine(stack.getString(sp + 2)); break; case ID_PARSE_INT: String s = stack.getString(sp + 2).Trim().ToLower(); try { if (stack.isNull(sp + 3)) { stack.setInt(sp, s.StartsWith("0x") ? Convert.ToInt32(s.Substring(2), 16) : Convert.ToInt32(s)); } else { stack.setInt(sp, Convert.ToInt32(s, stack.getInt(sp + 3))); } } catch (FormatException) { stack.setInt(sp, 0); } break; case ID_PARSE_FLOAT: try { stack.setNumber(sp, Convert.ToDouble(stack.getString(sp + 2))); } catch (FormatException) { stack.setNumber(sp, double.NaN); } break; case ID_IS_NAN: stack.setBoolean(sp, double.IsNaN(stack.getNumber(sp + 2))); break; case ID_IS_FINITE: double d_ = stack.getNumber(sp + 2); stack.setBoolean(sp, !Double.IsInfinity(d_) && !Double.IsNaN(d_)); break; case ID_DECODE_URI: obj = stack.getObject(sp + 2); if (obj is sbyte[]) { stack.setObject(sp, JsSystem.decodeURI((sbyte[])obj)); } else { stack.setObject(sp, JsSystem.decodeURI(obj.ToString())); } break; case ID_ENCODE_URI: obj = stack.getObject(sp + 2); if (obj is sbyte[]) { stack.setObject(sp, JsSystem.encodeURI((sbyte[])obj)); } else { stack.setObject(sp, JsSystem.encodeURI(obj.ToString())); } break; //TODO Implement case ID_DECODE_URI_COMPONENT: case ID_ENCODE_URI_COMPONENT: throw new Exception("NYI"); // math properties case ID_ABS: stack.setNumber(sp, Math.Abs(stack.getNumber(sp + 2))); break; case ID_ACOS: case ID_ASIN: case ID_ATAN: case ID_ATAN2: throw new Exception("NYI"); case ID_CEIL: stack.setNumber(sp, Math.Ceiling(stack.getNumber(sp + 2))); break; case ID_COS: stack.setNumber(sp, Math.Cos(stack.getNumber(sp + 2))); break; case ID_EXP: stack.setNumber(sp, JsSystem.exp(stack.getNumber(sp + 2))); break; case ID_FLOOR: stack.setNumber(sp, Math.Floor(stack.getNumber(sp + 2))); break; case ID_LOG: stack.setNumber(sp, JsSystem.ln(stack.getNumber(sp + 2))); break; case ID_MAX: double d2 = Double.NegativeInfinity; for (int i3 = 0; i3 < parCount; i3++) { d2 = Math.Max(d2, stack.getNumber(sp + 2 + i3)); } stack.setNumber(sp, d2); break; case ID_MIN: double d3 = Double.PositiveInfinity; for (int i4 = 0; i4 < parCount; i4++) { d3 = Math.Min(d3, stack.getNumber(sp + 2 + i4)); } stack.setNumber(sp, d3); break; case ID_POW: stack.setNumber(sp, JsSystem.pow(stack.getNumber(sp + 2), stack.getNumber(sp + 3))); break; case ID_RANDOM: stack.setNumber(sp, JsSystem.random.NextDouble()); break; case ID_ROUND: stack.setNumber(sp, Math.Floor(stack.getNumber(sp + 2) + 0.5)); break; case ID_SIN: stack.setNumber(sp, Math.Sin(stack.getNumber(sp + 2))); break; case ID_SQRT: stack.setNumber(sp, Math.Sqrt(stack.getNumber(sp + 2))); break; case ID_TAN: stack.setNumber(sp, Math.Tan(stack.getNumber(sp + 2))); break; // string methods case ID_FROM_CHAR_CODE: char[] chars = new char[parCount]; for (int i5 = 0; i5 < parCount; i5++) { chars[i5] = (char)stack.getInt(sp + 2 + i5); } stack.setObject(sp, new String(chars)); break; // string.prototype methods case ID_CHAR_AT: s = stack.getString(sp); int i6 = stack.getInt(sp + 2); stack.setObject(sp, i6 < 0 || i6 >= s.Length ? "" : s.Substring(i6, 1)); break; case ID_CHAR_CODE_AT: s = stack.getString(sp); int i7 = stack.getInt(sp + 2); stack.setNumber(sp, i7 < 0 || i7 >= s.Length ? Double.NaN : s[i7]); break; case ID_CONCAT: buf = new StringBuilder(stack.getString(sp)); for (int i8 = 0; i8 < parCount; i8++) { buf.Append(stack.getString(sp + i8 + 2)); } stack.setObject(sp, buf.ToString()); break; case ID_INDEX_OF: stack.setNumber(sp, stack.getString(sp).IndexOf(stack.getString(sp + 2), stack.getInt(sp + 3))); break; case ID_LAST_INDEX_OF: s = stack.getString(sp); String find = stack.getString(sp + 2); double d4 = stack.getNumber(sp + 3); int max = (Double.IsNaN(d4)) ? s.Length : (int)d4; int best = -1; while (true) { int found = s.IndexOf(find, best + 1); if (found == -1 || found > max) { break; } best = found; } stack.setNumber(sp, best); break; case ID_LOCALE_COMPARE: stack.setNumber(sp, stack.getString(sp).CompareTo(stack.getString(sp + 2))); break; case ID_REPLACE: s = stack.getString(sp); find = stack.getString(sp + 2); String replace = stack.getString(sp + 3); if (!find.Equals("")) { StringBuilder sb = new StringBuilder(s); int length = find.Length; // Parse nodes into vector while ((index = sb.ToString().IndexOf(find)) >= 0) { sb.Remove(index, index + length); sb.Insert(index, replace); } stack.setObject(sp, sb.ToString()); sb = null; } break; case ID_MATCH: case ID_SEARCH: throw new Exception("Regexp NYI"); case ID_SLICE: s = stack.getString(sp); int len = s.Length; int start = stack.getInt(sp + 2); int end = stack.isNull(sp + 3) ? len : stack.getInt(sp + 3); if (start < 0) { start = Math.Max(len + start, 0); } if (end < 0) { end = Math.Max(len + start, 0); } if (start > len) { start = len; } if (end > len) { end = len; } if (end < start) { end = start; } stack.setObject(sp, s.Substring(start, end - start)); break; case ID_SPLIT: s = stack.getString(sp); String sep = stack.getString(sp + 2); double limit = stack.getNumber(sp + 3); if (Double.IsNaN(limit) || limit < 1) { limit = Double.MaxValue; } JsArray a = new JsArray(); if (sep.Length == 0) { if (s.Length < limit) { limit = s.Length; } for (int i9 = 0; i9 < limit; i9++) { a.setObject(i9, s.Substring(i9, 1)); } } else { int cut0 = 0; while (cut0 < s.Length && a.size() < limit) { int cut = s.IndexOf(sep, cut0); if (cut == -1) { cut = s.Length; } a.setObject(a.size(), s.Substring(cut0, cut - cut0)); cut0 = cut + sep.Length; } } stack.setObject(sp, a); break; case ID_SUBSTRING: s = stack.getString(sp); len = s.Length; start = stack.getInt(sp + 2); end = stack.isNull(sp + 3) ? len : stack.getInt(sp + 3); if (start > end) { int tmp = end; end = start; start = tmp; } start = Math.Min(Math.Max(0, start), len); end = Math.Min(Math.Max(0, end), len); stack.setObject(sp, s.Substring(start, end - start)); break; case ID_TO_LOWER_CASE: //TODO: which locale to use as defautlt? us? case ID_TO_LOCALE_LOWER_CASE: stack.setObject(sp, stack.getString(sp + 2).ToLower()); break; case ID_TO_UPPER_CASE: //TODO: which locale to use as defautlt? us? case ID_TO_LOCALE_UPPER_CASE: stack.setObject(sp, stack.getString(sp + 2).ToUpper()); break; case ID_LENGTH: stack.setInt(sp, ToString().Length); break; case ID_LENGTH_SET: // cannot be changed! break; case ID_TO_EXPONENTIAL: case ID_TO_FIXED: case ID_TO_PRECISION: stack.setObject(sp, JsSystem.formatNumber(index, stack.getNumber(sp + 2), stack.getNumber(sp + 3))); break; case ID_UTC: JsDate date = new JsDate(JsDate.DATE_PROTOTYPE); date.time.setTime(new DateTime()); int year2 = stack.getInt(sp + 2); if (year2 >= 0 && year2 <= 99) { year2 += 1900; } date.setDate(true, year2, stack.getNumber(sp + 3), stack.getNumber(sp + 4)); date.setTime(true, stack.getNumber(sp + 5), stack.getNumber(sp + 6), stack.getNumber(sp + 7), stack.getNumber(sp + 8)); stack.setNumber(sp, date.time.getTime().Ticks); break; case ID_PARSE: double[] vals = { Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN }; s = stack.getString(sp + 2); int curr = -1; int pos = 0; for (int i10 = 0; i10 < s.Length; i10++) { char c = s[i10]; if (c >= '0' && c <= '9') { if (curr == -1) { curr = c - 48; } else { curr = curr * 10 + (c - 48); } } else if (curr != -1) { if (pos < vals.Length) { vals[pos++] = curr; } curr = -1; } } if (curr != -1 && pos < vals.Length) { vals[pos++] = curr; } bool utc = s.EndsWith("GMT") || s.EndsWith("UTC"); date = new JsDate(JsDate.DATE_PROTOTYPE); date.time.setTime(new DateTime()); date.setDate(utc, vals[0], vals[1], vals[2]); date.setTime(utc, vals[3], vals[4], vals[5], vals[6]); stack.setNumber(sp, date.time.getTime().Ticks); break; // Math constants case ID_E: stack.setNumber(sp, Math.E); break; case ID_LN10: stack.setNumber(sp, 2.302585092994046); break; case ID_LN2: stack.setNumber(sp, JsSystem.LN2); break; case ID_LOG2E: stack.setNumber(sp, 1.4426950408889634); break; case ID_LOG10E: stack.setNumber(sp, 0.4342944819032518); break; case ID_PI: stack.setNumber(sp, Math.PI); break; case ID_SQRT1_2: stack.setNumber(sp, Math.Sqrt(0.5)); break; case ID_SQRT2: stack.setNumber(sp, Math.Sqrt(2.0)); break; case ID_E_SET: case ID_LN10_SET: case ID_LN2_SET: case ID_LOG2E_SET: case ID_LOG10E_SET: case ID_PI_SET: case ID_SQRT1_2_SET: case ID_SQRT2_SET: // dont do anything: cannot overwrite those values! break; default: throw new ArgumentException("Unknown native id: " + index + " this: " + this); } }
/** * Creates a new arguments object. * * @param fn the function this arguments object holds the parameters for * @param context the evaluation context */ public JsArguments(JsFunction fn, JsObject context) : base(ARGUMENTS_PROTOTYPE) { this.function = fn; this.context = context; }
/** * Evaluate this function. The this-pointer, function object and parameters * must be on stack (sp + 0 = context, sp + 1=function, sp + 2 = first param * etc.). The result is expected at sp + 0. */ public void eval(JsArray stack, int sp, int actualParameterCount) { for (int i = actualParameterCount; i < expectedParameterCount; i++) { stack.setObject(sp + i + 2, null); } JsObject thisPtr = stack.getJsObject(sp); if (byteCode == null) { thisPtr.evalNative(index, stack, sp, actualParameterCount); return; } // sp initially points to context // bp points to parameter 0. context is at bp-2, lambda at bp-1 sp += 2; int bp = sp; JsObject context; // note: arguments available here only! if (localNames != null) { context = new JsObject(JsObject.OBJECT_PROTOTYPE); context.scopeChain = this.context; JsArguments args = new JsArguments(this, context); for (int i = 0; i < expectedParameterCount; i++) { context.addVar(localNames[i], stack.getObject(sp + i)); args.addVar("" + i, (Int32)(i)); } for (int i = expectedParameterCount; i < this.localNames.Length; i++) { context.addVar(localNames[i], null); } for (int i = expectedParameterCount; i < actualParameterCount; i++) { args.setObject("" + i, stack.getObject(bp + i)); } args.setNumber("length", actualParameterCount); args.setObject("callee", this); context.addVar("arguments", args); } else { context = this.context; sp += expectedParameterCount + varCount; } int initialSp = sp; int opcode; sbyte[] bytecode = this.byteCode; int pc = 0; int end = bytecode.Length; try { while (pc < end) { opcode = bytecode[pc++]; if (opcode < 0) { int imm; if ((opcode & 1) == 0) { imm = bytecode[pc++]; } else { imm = (bytecode[pc] << 8) | (bytecode[pc + 1] & 255); pc += 2; } switch ((opcode & 0x0ff) >> 1) { case XOP_ADD: stack.setNumber(sp - 1, stack.getNumber(sp - 1) + imm); break; case XOP_TRY_CALL: try { sp = sp - imm - 2; // on stack: context, lambda, params JsFunction m1 = (JsFunction)stack.getObject(sp + 1); m1.eval(stack, sp, imm); stack.setBoolean(sp + 1, true); sp += 2; } catch (JsException e) { stack.setObject(sp++, e.getError()); stack.setBoolean(sp++, false); // not successfull } catch (Exception e) { stack.setObject(sp++, new JsError(e)); stack.setBoolean(sp++, false); // not successfull } break; case XOP_CALL: sp = sp - imm - 2; // on stack: context, lambda, params JsFunction m2 = (JsFunction)stack.getObject(sp + 1); m2.eval(stack, sp++, imm); // System.out.println("Ret val received: " // + stack.getObject(sp-1)+" sp: "+sp); break; case XOP_PUSH_FN: stack.setObject(sp++, new JsFunction(functionLiterals[imm], context)); break; case XOP_GO: pc += imm; break; case XOP_IF: if (!stack.getBoolean(--sp)) { pc += imm; } break; case XOP_PUSH_INT: stack.setNumber(sp++, imm); break; case XOP_LCL_GET: stack.copy(bp + imm, stack, sp++); break; // case XOP_LOCAL_INC: // stack.setFP(sp - 1, stack.getFP(sp - 1) // + stack.getFP(bp + imm)); // // fall-through! case XOP_LCL_SET: stack.copy(sp - 1, stack, bp + imm); break; case XOP_NEXT: IEnumerator en = (IEnumerator)stack.getObject(sp - 1); if (en.MoveNext()) { stack.setObject(sp++, en.Current); } else { pc += imm; } break; case XOP_PUSH_NUM: stack.setNumber(sp++, numberLiterals[imm]); break; case XOP_PUSH_STR: // System.out.println("String:" + stringList[(int)param]); stack.setObject(sp++, stringLiterals[imm]); break; default: throw new Exception("Illegal opcode: " + MyInteger.toString(opcode & 0xff, 16) + " par: " + imm); } // switch } else { switch (opcode) { case OP_ADD: if (stack.isNumber(sp - 2) && stack.isNumber(sp - 1)) { stack.setNumber(sp - 2, stack.getNumber(sp - 2) + stack.getNumber(sp - 1)); } else { stack.setObject(sp - 2, stack.getString(sp - 2) + stack.getString(sp - 1)); } sp--; break; case OP_AND: stack.setNumber(sp - 2, stack.getInt(sp - 2) & stack.getInt(sp - 1)); sp--; break; case OP_APPEND: JsArray arr = (JsArray)stack.getObject(sp - 2); stack.copy(sp - 1, arr, arr.size()); // ((Array) // stack.getObject(sp-2)).addElement(stack.getObject(sp-1)); sp--; break; case OP_ASR: stack.setNumber(sp - 2, (stack.getInt(sp - 2) & 0xffffffffL) >> (stack.getInt(sp - 1) & 0x1f)); sp--; break; case OP_CTX_GET: context.vmGetOperation(stack, sp - 1, sp - 1); break; case OP_CTX_SET: context.vmSetOperation(stack, sp - 1, sp - 2); sp--; // take away name, not value break; case OP_CTX: stack.setObject(sp++, context); break; case OP_DEC: stack.setNumber(sp - 1, stack.getNumber(sp - 1) - 1); break; case OP_DEL: stack.setBoolean(sp - 2, stack.getJsObject(sp - 2).delete(stack.getString(sp - 1))); sp--; break; case OP_DIV: stack.setNumber(sp - 2, stack.getNumber(sp - 2) / stack.getNumber(sp - 1)); sp--; break; case OP_DROP: sp--; break; case OP_DUP: stack.copy(sp - 1, stack, sp); sp++; break; case OP_DDUP: stack.copy(sp - 2, stack, sp, 2); sp += 2; break; case OP_ENUM: stack.setObject(sp - 1, ((JsObject)stack.getObject(sp - 1)).keys()); break; case OP_EQEQEQ: if (stack.getType(sp - 2) != stack.getType(sp - 1)) { sp--; stack.setObject(sp - 1, false); break; } // fall-trough goto case OP_EQEQ; case OP_EQEQ: // System.out.println(""+stack.getObject(sp-2)+ " = "+ // stack.getObject(sp-1)); int tX = stack.getType(sp - 2); int tY = stack.getType(sp - 1); if (tX == tY) { switch (tX) { case TYPE_UNDEFINED: case TYPE_NULL: stack.setObject(sp - 2, true); break; case TYPE_NUMBER: stack.setBoolean(sp - 2, stack.getNumber(sp - 2) == stack.getNumber(sp - 1)); break; default: stack.setBoolean(sp - 2, stack.getObject(sp - 2).Equals( stack.getObject(sp - 1))); break; } } else { bool result; if ((tX == TYPE_UNDEFINED && tY == TYPE_NULL) || (tX == TYPE_NULL && tY == TYPE_UNDEFINED)) { result = true; } else if (tX == TYPE_NUMBER || tY == TYPE_NUMBER) { result = stack.getNumber(sp - 2) == stack.getNumber(sp - 1); } else if ((tX == TYPE_STRING && tY == TYPE_OBJECT) || tX == TYPE_OBJECT && tY == TYPE_STRING) { result = stack.getString(sp - 2) .Equals(stack.getString(sp - 1)); } else { result = false; } stack.setBoolean(sp - 2, result); } sp--; break; case OP_GET: JsObject ctx = stack.getJsObject(sp - 2); // System.out.println("GetMember ctx: "+ctx); // System.out.println("GetMember name: " + stack.getObject(sp - 1)); ctx.vmGetOperation(stack, sp - 1, sp - 2); sp--; break; case OP_GT: if (stack.isNumber(sp - 2) && stack.isNumber(sp - 1)) { stack.setObject(sp - 2, stack.getNumber(sp - 2) > stack.getNumber(sp - 1) ? true : false); } else { stack.setObject(sp - 2, stack.getString(sp - 2).CompareTo(stack.getString(sp - 1)) > 0 ? true : false); } sp--; break; case OP_IN: Object o = stack.getObject(sp - 1); if (o is JsArray && stack.isNumber(sp - 2)) { int i = stack.getInt(sp - 2); stack.setObject(sp - 2, i >= 0 && i <= ((JsArray)o).size() ? true : false); sp--; break; } if (o is JsObject) { stack.setObject(sp - 2, ((JsObject)o).getRawInPrototypeChain(stack.getString(sp - 2)) == null ? true : false); sp--; break; } stack.setObject(sp - 2, false); sp--; break; case OP_INC: stack.setNumber(sp - 1, stack.getNumber(sp - 1) + 1); break; case OP_INV: stack.setInt(sp - 1, ~stack.getInt(sp - 1)); break; case OP_LT: if (stack.isNumber(sp - 2) && stack.isNumber(sp - 1)) { stack.setObject(sp - 2, stack.getNumber(sp - 2) < stack.getNumber(sp - 1) ? true : false); } else { stack.setObject(sp - 2, stack.getString(sp - 2). CompareTo(stack.getString(sp - 1)) < 0 ? true : false); } sp--; break; case OP_MOD: stack.setNumber(sp - 2, (stack.getNumber(sp - 2) % (stack.getNumber(sp - 1)))); sp--; break; case OP_MUL: stack.setNumber(sp - 2, stack.getNumber(sp - 2) * stack .getNumber(sp - 1)); sp--; break; case OP_NEW_ARR: stack.setObject(sp++, new JsArray()); break; case OP_NEW: JsFunction constructor = ((JsFunction)stack.getObject(sp - 1)); ctx = constructor.factory.newInstance(constructor.factoryTypeId); stack.setObject(sp - 1, ctx); stack.setObject(sp++, ctx); stack.setObject(sp++, constructor); break; case OP_NEW_OBJ: stack.setObject(sp++, new JsObject(OBJECT_PROTOTYPE)); break; case OP_NEG: stack.setNumber(sp - 1, -stack.getNumber(sp - 1)); break; case OP_NOT: stack.setObject(sp - 1, stack.getBoolean(sp - 1) ? false : true); break; case OP_OR: stack.setNumber(sp - 2, stack.getInt(sp - 2) | stack.getInt(sp - 1)); sp--; break; case OP_PUSH_FALSE: stack.setObject(sp++, false); break; case OP_PUSH_GLOBAL: stack.setObject(sp++, stack.getObject(0)); break; case OP_PUSH_NULL: stack.setObject(sp++, JsSystem.JS_NULL); break; case OP_PUSH_THIS: stack.setObject(sp++, thisPtr); break; case OP_PUSH_TRUE: stack.setObject(sp++, true); break; case OP_PUSH_UNDEF: stack.setObject(sp++, null); break; case OP_RET: // System.out.println("sp: "+sp+" returning: // "+stack.getObject(sp-1)); stack.copy(sp - 1, stack, bp - 2); return; case OP_ROT: stack.copy(sp - 3, stack, sp - 2, 3); stack.copy(sp, stack, sp - 3); break; case OP_SET_KC: // ctx: sp-3 // property name: sp-2 // value to set: sp-1; ctx = stack.getJsObject(sp - 3); ctx.vmSetOperation(stack, sp - 2, sp - 1); // key = (String) stack.getObject(sp-2); // Object curr = ctx.getRaw(key); // System.out.println("SetMember KC ctx: "+ctx); // System.out.println("SetMember name: "+stack.getObject(sp-2)); // System.out.println("SetMember value: "+stack.getObject(sp-1)); sp -= 2; // leave value on the stack(!) break; case OP_SET: ctx = stack.getJsObject(sp - 2); ctx.vmSetOperation(stack, sp - 1, sp - 3); // key = (String) stack.getObject(sp-1); // curr = ctx.getRaw(key); // System.out.println("SetMember KV ctx: "+ctx); // System.out.println("SetMember name: "+stack.getObject(sp-1)); // System.out.println("SetMember value: "+stack.getObject(sp-3)); sp -= 2; // leave value on the stack(!) break; case OP_SHR: stack.setNumber(sp - 2, stack.getInt(sp - 2) >> (stack.getInt(sp - 1) & 0x1f)); sp--; break; case OP_SHL: stack.setNumber(sp - 2, stack.getInt(sp - 2) << (stack.getInt(sp - 1) & 0x1f)); sp--; break; case OP_SUB: stack.setNumber(sp - 2, stack.getNumber(sp - 2) - stack.getNumber(sp - 1)); sp--; break; case OP_SWAP: stack.swap(sp - 1, sp - 2); break; case OP_THROW: // line number is added in try..catch below throw new JsException(stack.getJsObject(sp)); case OP_WITH_START: JsObject nc = new JsObject((JsObject)stack.getObject(sp - 1)); nc.scopeChain = context; context = nc; sp--; break; case OP_WITH_END: context = context.scopeChain; break; case OP_TYPEOF: stack.setObject(sp - 1, TYPE_NAMES[stack.getType(sp - 1)]); break; case OP_INSTANCEOF: o = stack.getObject(sp - 2); JsObject p = stack.getJsObject(sp - 1); if (p is JsFunction && o is JsObject) { JsObject j = ((JsObject)o); p = ((JsFunction)p).prototype; while (j.__proto__ != null && j.__proto__ != p) { j = j.__proto__; } stack.setBoolean(sp - 2, j != null); } else { stack.setObject(sp - 2, false); } sp--; break; case OP_XOR: stack.setNumber(sp - 2, stack.getInt(sp - 2) ^ stack.getInt(sp - 1)); sp--; break; default: throw new Exception("Illegal opcode: '" + ((char)opcode) + "'/" + opcode); } } } } catch (Exception e) { JsException jse; if (e is JsException) { jse = (JsException)e; } else { Console.WriteLine(e.StackTrace); jse = new JsException(e); } if (jse.pc == -1) { jse.pc = pc - 1; jse.lineNumber = getLineNumber(pc - 1); } throw jse; } if (sp == initialSp + 1) { // System.out.println("sp: "+sp+" returning: "+stack.getObject(sp-1)); stack.copy(sp - 1, stack, bp - 2); } else if (sp == initialSp) { // System.out.println("sp: "+sp+" returning NULL"); stack.setObject(bp - 2, null); } else { throw new Exception("too much or too little on the stack; sp: " + sp + " bp: " + bp + " varCount: " + varCount + " parCount: " + actualParameterCount); } return; }
/** * Constructs a function literal from the serialized binary form including the * string table. Please note that function literals cannot be invoked * directly, a function must be created from this function literal using the * corresponding constructor. If the global string table is null, the file * header is expected. * * @throws IOException thrown for underlying stream IO errors */ public JsFunction(DataInputStream dis, string[] globalStringTable) : base(FUNCTION_PROTOTYPE) { // __proto__ above, prototype below... this.prototype = new JsObject(OBJECT_PROTOTYPE); sbyte[] buf = null; int flags = 0; //loop: while (true) { int blockType = dis.read(); int count; switch (blockType) { case BLOCK_COMMENT: count = dis.readUnsignedShort(); if (buf == null || buf.Length < count) { buf = new sbyte[count]; } dis.readFully(buf, 0, count); break; case BLOCK_GLOBAL_STRING_TABLE: count = dis.readUnsignedShort(); globalStringTable = new String[count]; for (int i = 0; i < count; i++) { globalStringTable[i] = dis.readUTF(); } break; case BLOCK_STRING_LITERALS: count = dis.readUnsignedShort(); stringLiterals = new String[count]; for (int i = 0; i < count; i++) { stringLiterals[i] = globalStringTable[dis.readShort()]; } break; case BLOCK_NUMBER_LITERALS: count = dis.readUnsignedShort(); numberLiterals = new double[count]; for (int i = 0; i < count; i++) { numberLiterals[i] = dis.readDouble(); } break; case BLOCK_FUNCTION_LITERALS: count = dis.readUnsignedShort(); functionLiterals = new JsFunction[count]; for (int i = 0; i < count; i++) { functionLiterals[i] = new JsFunction(dis, globalStringTable); } break; case BLOCK_LOCAL_VARIABLE_NAMES: count = dis.readUnsignedShort(); localNames = new String[count]; for (int i = 0; i < count; i++) { localNames[i] = globalStringTable[dis.readShort()]; } break; case BLOCK_BYTE_CODE: varCount = dis.readUnsignedShort(); expectedParameterCount = dis.readUnsignedShort(); varCount -= expectedParameterCount; flags = dis.read(); byteCode = new sbyte[dis.readShort()]; dis.readFully(byteCode); break; case BLOCK_LINE_NUMBERS: count = dis.readUnsignedShort(); lineNumbers = new int[count * 2]; for (int i = 0; i < count; i++) { lineNumbers[i << 1] = dis.readUnsignedShort(); lineNumbers[(i << 1) + 1] = dis.readUnsignedShort(); } break; case END_MARKER: goto loopBreak; default: throw new IOException("Illegal Block type " + MyInteger.toString(blockType, 16)); } } loopBreak: if ((flags & 1) == 0) { if (localNames == null) { localNames = new String[0]; } } else { localNames = null; } }