jsexn on stack overflow
[org.ibex.core.git] / src / org / ibex / js / Interpreter.java
index 59b48c3..6fc2859 100644 (file)
@@ -6,7 +6,7 @@ import java.util.*;
 
 /** Encapsulates a single JS interpreter (ie call stack) */
 class Interpreter implements ByteCodes, Tokens {
-
+    private static final int MAX_STACK_SIZE = 512;
 
     // Thread-Interpreter Mapping /////////////////////////////////////////////////////////////////////////
 
@@ -217,16 +217,25 @@ class Interpreter implements ByteCodes, Tokens {
                     throw je("tried to assign \"" + (val==null?"(null)":val.toString()) + "\" to the null key");
 
                 Trap t = null;
-                if (target instanceof Trap.TrapScope && key.equals("cascade")) {
-                    Trap.TrapScope ts = (Trap.TrapScope)target;
-                    t = ts.t.next;
-                    ts.cascadeHappened = true;
+                if (target instanceof JSScope && key.equals("cascade")) {
+                   Trap.TrapScope ts = null;
+                    JSScope p = (JSScope)target; // search the scope-path for the trap
+                   if (target instanceof Trap.TrapScope) {
+                       ts = (Trap.TrapScope)target;
+                   }
+                   else {
+                       while (ts == null && p.getParentScope() != null) {
+                           p = p.getParentScope();
+                           if (p instanceof Trap.TrapScope) {
+                               ts = (Trap.TrapScope)p;
+                           }
+                       }
+                   }
+                   t = ts.t.next;
+                   ts.cascadeHappened = true;
                     while (t != null && t.f.numFormalArgs == 0) t = t.next;
                     if (t == null) { target = ts.t.trapee; key = ts.t.name; }
 
-                } else if (target instanceof Trap.TrapScope && key.equals(((Trap.TrapScope)target).t.name)) {
-                    throw je("tried to put to " + key + " inside a trap it owns; use cascade instead"); 
-
                 } else if (target instanceof JS) {
                     if (target instanceof JSScope) {
                         JSScope p = (JSScope)target; // search the scope-path for the trap
@@ -235,16 +244,14 @@ class Interpreter implements ByteCodes, Tokens {
                     } else {
                         t = ((JS)target).getTrap(key);
                     }
-
                     while (t != null && t.f.numFormalArgs == 0) t = t.next; // find the first write trap
-                    if (t != null) {
-                        stack.push(new CallMarker(this));
-                        JSArray args = new JSArray();
-                        args.addElement(val);
-                        stack.push(args);
-                    }
                 }
                 if (t != null) {
+                    stack.push(new CallMarker(this));
+                    if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow");
+                    JSArray args = new JSArray();
+                    args.addElement(val);
+                    stack.push(args);
                     f = t.f;
                     scope = new Trap.TrapScope(f.parentScope, t, val);
                     pc = -1;
@@ -289,15 +296,13 @@ class Interpreter implements ByteCodes, Tokens {
                         } else {
                             t = ((JS)o).getTrap(v);
                         }
-
                         while (t != null && t.f.numFormalArgs != 0) t = t.next; // get first read trap
-                        if (t != null) {
-                            stack.push(new CallMarker(this));
-                            JSArray args = new JSArray();
-                            stack.push(args);
-                        }
                     }
                     if (t != null) {
+                        stack.push(new CallMarker(this));
+                        if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow");
+                        JSArray args = new JSArray();
+                        stack.push(args);
                         f = t.f;
                         scope = new Trap.TrapScope(f.parentScope, t, null);
                         ((Trap.TrapScope)scope).cascadeHappened = true;
@@ -346,6 +351,7 @@ class Interpreter implements ByteCodes, Tokens {
                     JSArray arguments = new JSArray();
                     for(int i=0; i<numArgs; i++) arguments.addElement(i==0?a0:i==1?a1:i==2?a2:rest[i-3]);
                     stack.push(new CallMarker(this));
+                    if(stack.size() > MAX_STACK_SIZE) throw new JSExn("stack overflow");
                     stack.push(arguments);
                     f = (JSFunction)object;
                     scope = new JSScope(f.parentScope);
@@ -366,8 +372,9 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
             case THROW:
-                throw new JSExn(stack.pop());
+                throw new JSExn(stack.pop(), stack, f, pc, scope);
 
+                /* FIXME
             case MAKE_GRAMMAR: {
                 final Grammar r = (Grammar)arg;
                 final JSScope final_scope = scope;
@@ -389,13 +396,23 @@ class Interpreter implements ByteCodes, Tokens {
                 stack.push(r2);
                 break;
             }
-
+                */
             case ADD_TRAP: case DEL_TRAP: {
                 Object val = stack.pop();
                 Object key = stack.pop();
                 Object obj = stack.peek();
                 // A trap addition/removal
-                JS js = obj instanceof JSScope ? ((JSScope)obj).top() : (JS) obj;
+                JS js = (JS) obj;
+                if(js instanceof JSScope) {
+                    JSScope s = (JSScope) js;
+                    while(s.getParentScope() != null) {
+                        if(s.has(key)) throw new JSExn("cannot trap a variable that isn't at the top level scope");
+                        s = s.getParentScope();
+                    }
+                    js = s;
+                }
+                // might want this?
+                // if(!js.has(key)) throw new JSExn("tried to add/remove a trap from an uninitialized variable");
                 if(op == ADD_TRAP) js.addTrap(key, (JSFunction)val);
                 else js.delTrap(key, (JSFunction)val);
                 break;
@@ -479,7 +496,7 @@ class Interpreter implements ByteCodes, Tokens {
                     if (right == null) right = JS.N(0);
                     int result = 0;
                     if (left instanceof String || right instanceof String) {
-                        result = left.toString().compareTo(right.toString());
+                        result = JS.toString(left).compareTo(JS.toString(right));
                     } else {
                         result = (int)java.lang.Math.ceil(JS.toDouble(left) - JS.toDouble(right));
                     }
@@ -490,6 +507,7 @@ class Interpreter implements ByteCodes, Tokens {
                     
                 case EQ:
                 case NE: {
+                    // FIXME: This is not correct, see ECMA-262 11.9.3
                     Object l = left;
                     Object r = right;
                     boolean ret;
@@ -498,7 +516,7 @@ class Interpreter implements ByteCodes, Tokens {
                     else if (r == null) ret = false; // l != null, so its false
                     else if (l instanceof Boolean) ret = JS.B(JS.toBoolean(r)).equals(l);
                     else if (l instanceof Number) ret = JS.toNumber(r).doubleValue() == JS.toNumber(l).doubleValue();
-                    else if (l instanceof String) ret = r != null && l.equals(r.toString());
+                    else if (l instanceof String) ret = r != null && l.equals(JS.toString(r));
                     else ret = l.equals(r);
                     stack.push(JS.B(op == EQ ? ret : !ret)); break;
                 }
@@ -508,7 +526,6 @@ class Interpreter implements ByteCodes, Tokens {
             }
 
         } catch(JSExn e) {
-            if(f.op[pc] != FINALLY_DONE) e.addBacktrace(f.sourceName,f.line[pc]);
             while(stack.size() > 0) {
                 Object o = stack.pop();
                 if (o instanceof CatchMarker || o instanceof TryMarker) {
@@ -533,12 +550,6 @@ class Interpreter implements ByteCodes, Tokens {
                         pc = ((TryMarker)o).finallyLoc - 1;
                         continue OUTER;
                     }
-                } else if(o instanceof CallMarker) {
-                    CallMarker cm = (CallMarker) o;
-                    if(cm.f == null)
-                        e.addBacktrace("<java>",0); // This might not even be worth mentioning
-                    else
-                        e.addBacktrace(cm.f.sourceName,cm.f.line[cm.pc-1]);
                 }
             }
             throw e;
@@ -652,13 +663,13 @@ class Interpreter implements ByteCodes, Tokens {
             return sb.toString();
         }
         case "indexOf": {
-            String search = alength >= 1 ? arg0.toString() : "null";
+            String search = alength >= 1 ? JS.toString(arg0) : "null";
             int start = alength >= 2 ? JS.toInt(arg1) : 0;
             // Java's indexOf handles an out of bounds start index, it'll return -1
             return JS.N(s.indexOf(search,start));
         }
         case "lastIndexOf": {
-            String search = alength >= 1 ? arg0.toString() : "null";
+            String search = alength >= 1 ? JS.toString(arg0) : "null";
             int start = alength >= 2 ? JS.toInt(arg1) : 0;
             // Java's indexOf handles an out of bounds start index, it'll return -1
             return JS.N(s.lastIndexOf(search,start));            
@@ -689,14 +700,14 @@ class Interpreter implements ByteCodes, Tokens {
     static Object getFromPrimitive(Object o, Object key) throws JSExn {
         boolean returnJS = false;
         if (o instanceof Boolean) {
-            throw new JSExn("cannot call methods on Booleans");
+            throw new JSExn("Booleans do not have properties");
         } else if (o instanceof Number) {
             if (key.equals("toPrecision") || key.equals("toExponential") || key.equals("toFixed"))
                 returnJS = true;
         }
         if (!returnJS) {
             // the string stuff applies to everything
-            String s = o.toString();
+            String s = JS.toString(o);
             
             // this is sort of ugly, but this list should never change
             // These should provide a complete (enough) implementation of the ECMA-262 String object
@@ -711,7 +722,7 @@ class Interpreter implements ByteCodes, Tokens {
             case "lastIndexOf": returnJS = true; break; 
             case "match": returnJS = true; break; 
             case "replace": returnJS = true; break; 
-            case "seatch": returnJS = true; break; 
+            case "search": returnJS = true; break; 
             case "slice": returnJS = true; break; 
             case "split": returnJS = true; break; 
             case "toLowerCase": returnJS = true; break; 
@@ -722,7 +733,7 @@ class Interpreter implements ByteCodes, Tokens {
         }
         if (returnJS) {
             final Object target = o;
-            final String method = key.toString();
+            final String method = JS.toString(o);
             return new JS() {
                     public Object call(Object a0, Object a1, Object a2, Object[] rest, int nargs) throws JSExn {
                         if (nargs > 2) throw new JSExn("cannot call that method with that many arguments");