CVE-2020-22875: Integer Overflow or Wraparound (CWE-190)
value: Integer overflow vulnerability in function Jsi_ObjSetLength in jsish before 3.0.6, allows remote attackers to execute arbitrary code.
CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 9.8
Published: 2021-07-13T15:15Z
Repository: https://github.com/pcmacdon/jsish
Language: C
Files changed: md/Reference.md, src/jsi.h, src/jsiArray.c, src/jsiCmds.c, tests/locals.jsi, tools/protos.jsi, www/reference.wiki
Commit date: 2020-04-13 16:07:27 +0000
Commit message: Release "3.0.6": Fix crashes in Array for "integer overflow #10"

FossilOrigin-Name: 50642d64b8f0a4e62df68e59fcb2ef0923c3dbec8fc8a15d38b1ddbe655c38d6
Diff: diff --git a/md/Reference.md b/md/Reference.md
index f61a8a8..f8313ea 100644
--- a/md/Reference.md
+++ b/md/Reference.md
@@ -517,11 +517,11 @@ Otherwise second argument must be a set of options.</td></tr>
 <tr><td>isMain</td><td>isMain():boolean </td><td>Return true if current script was the main script invoked from command-line.</td></tr>
 <tr><td>keywords</td><td>keywords(isSql=false, name:string=void):boolean|array </td><td>Return/lookup reserved keyword.</td></tr>
 <tr><td>level</td><td>level(level:number=void):number|array|object </td><td>Return current level or details of a call-stack frame. With no arg, returns the number of the current stack frame level.
 Otherwise returns details on the specified level.
 The topmost level is 1, and 0 is the current level, and a negative level translates as relative to the current level.</td></tr>
-<tr><td>locals</td><td>locals(varsOnly:boolean=void):object </td><td>Return functions/vars inside local function.</td></tr>
+<tr><td>locals</td><td>locals(filter:boolean=void):object </td><td>Return locals; use filter=true/false just vars/functions.</td></tr>
 <tr><td>lookup</td><td>lookup(name:string) </td><td>Given string name, lookup and return value, eg: function.</td></tr>
 <tr><td>methods</td><td>methods(val:string|regexp):array|object </td><td>Return functions and commands.</td></tr>
 <tr><td>named</td><td>named(name:string=void):array|userobj </td><td>Returns command names for builtin Objects, eg: 'File', 'Interp', sub-Object names, or the named object.</td></tr>
 <tr><td>options</td><td>options(ctype:boolean=false):array </td><td>Return Option type name, or with true the C type.</td></tr>
 <tr><td>package</td><td>package(pkgName:string):object|null </td><td>Return info about provided package if exists, else null.</td></tr>
@@ -1326,10 +1326,11 @@ By default, returns the string output, unless the 'bg', 'inputStr', 'retCode' or
 <tr><td>require</td><td>require(name:string=void, version:number|string=1, <a href='#System.requireOptions'>options</a>:object=void):number|array|object </td><td>Load/query packages. With no arguments, returns the list of all loaded packages.
 With one argument, loads the package (if necessary) and returns its version.
 With two arguments, returns object containing: version, loadFile, func.
 A third argument sets options for package or module.
 Note an error is thrown if requested version is greater than actual version.</td></tr>
+<tr><td>runMain</td><td>runMain(cmd:string|null|function=void, conf:array=undefined) </td><td>If isMain invokes runModule.</td></tr>
 <tr><td>runModule</td><td>runModule(cmd:string|null|function=void, conf:array=undefined) </td><td>Invoke named module. If name is empty, uses file basename. If isMain and no args givine parses console.args.</td></tr>
 <tr><td>setInterval</td><td>setInterval(callback:function, ms:number):number </td><td>Setup recurring function to run every given millisecs.</td></tr>
 <tr><td>setTimeout</td><td>setTimeout(callback:function, ms:number):number </td><td>Setup function to run after given millisecs.</td></tr>
 <tr><td>sleep</td><td>sleep(secs:number=1.0):void </td><td>sleep for N milliseconds, minimum .001.</td></tr>
 <tr><td>source</td><td>source(val:string|array, <a href='#System.sourceOptions'>options</a>:object=void) </td><td>Load and evaluate source files: trailing '/' appends PARENTDIR.jsi.</td></tr>
diff --git a/src/jsi.h b/src/jsi.h
index ad051d3..19b4d59 100644
--- a/src/jsi.h
+++ b/src/jsi.h
@@ -2,11 +2,11 @@
 #ifndef __JSI_H__
 #define __JSI_H__
 
 #define JSI_VERSION_MAJOR   3
 #define JSI_VERSION_MINOR   0
-#define JSI_VERSION_RELEASE 4
+#define JSI_VERSION_RELEASE 6
 
 #define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))
 
 #ifndef JSI_EXTERN
 #define JSI_EXTERN extern
diff --git a/src/jsiArray.c b/src/jsiArray.c
index 94dd1b3..298ec75 100644
--- a/src/jsiArray.c
+++ b/src/jsiArray.c
@@ -5,10 +5,16 @@
 
 #if JSI__MUSL==1 || defined(__FreeBSD__)
 #define NO_QSORT_R 1
 #endif
 
+static uint jsi_SizeOfArray(Jsi_Interp *interp, Jsi_Obj *obj) {
+    if (!obj || !obj->arr)
+        return 0;
+    return obj->arrCnt;
+}
+
 static Jsi_RC jsi_ArrayPushCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
     Jsi_Value **ret, Jsi_Func *funcPtr)
 {
     Jsi_Obj *obj;
     
@@ -17,23 +23,19 @@ static Jsi_RC jsi_ArrayPushCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_
         return JSI_OK;
     }
     obj = _this->d.obj;
     
     int argc = Jsi_ValueGetLength(interp, args);
-    int curlen = Jsi_ObjGetLength(interp, obj);
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
-    
+    int curlen = jsi_SizeOfArray(interp, obj);    
     int i;
     for (i = 0; i < argc; ++i) {
         Jsi_Value *ov = Jsi_ValueArrayIndex(interp, args, i);
         if (!ov) { Jsi_LogBug("Arguments Error"); ov = Jsi_ValueNew(interp); }
         Jsi_ValueInsertArray(interp, _this, curlen + i, ov, 0);
     }
     
-    Jsi_ValueMakeNumber(interp, ret, Jsi_ObjGetLength(interp, obj));
+    Jsi_ValueMakeNumber(interp, ret, jsi_SizeOfArray(interp, obj));
     return JSI_OK;
 }
 
 static Jsi_RC jsi_ArrayPopCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
     Jsi_Value **ret, Jsi_Func *funcPtr)
@@ -43,11 +45,11 @@ static Jsi_RC jsi_ArrayPopCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_t
         return JSI_OK;
     }
     Jsi_Value *v;
     Jsi_Obj *obj;
     obj = _this->d.obj;
-    int i = Jsi_ObjGetLength(interp, obj) - 1;
+    int i = jsi_SizeOfArray(interp, obj) - 1;
 
     if (i < 0) {
         Jsi_ValueMakeUndef(interp, ret);
         return JSI_OK;
     }
@@ -76,22 +78,22 @@ static Jsi_RC jsi_ArrayJoinCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_
         return Jsi_LogError("expected array object");
     const char *jstr = "";
     int argc, curlen;
     Jsi_DString dStr = {};
 
-    curlen = Jsi_ObjGetLength(interp, _this->d.obj);
+    curlen = jsi_SizeOfArray(interp, _this->d.obj);
     if (curlen == 0) {
         goto bail;
     }
 
     if (Jsi_ValueGetLength(interp, args) >= 1) {
         Jsi_Value *sc = Jsi_ValueArrayIndex(interp, args, 0);
         if (sc != NULL)
             jstr = Jsi_ValueToString(interp, sc, NULL);
     }
     
-    if (0 == (argc=Jsi_ObjGetLength(interp, _this->d.obj))) {
+    if (0 == (argc=jsi_SizeOfArray(interp, _this->d.obj))) {
         goto bail;
     }
     int i;
     for (i = 0; i < argc; ++i) {
         const char *cp;
@@ -231,11 +233,11 @@ void Jsi_ValueArrayShift(Jsi_Interp *interp, Jsi_Value *v)
         }
         o->arr[o->arrCnt--] = NULL;
         return;
     }
     
-    int len = Jsi_ObjGetLength(interp, v->d.obj);
+    int len = jsi_SizeOfArray(interp, v->d.obj);
     if (len <= 0) return;
     
     Jsi_Value *v0 = Jsi_ValueArrayIndex(interp, v, 0);
     if (!v0) return;
     
@@ -253,14 +255,14 @@ void Jsi_ValueArrayShift(Jsi_Interp *interp, Jsi_Value *v)
     Jsi_ObjSetLength(interp, v->d.obj, len - 1);
 }
 
 static Jsi_RC jsi_ArrayFlatSub(Jsi_Interp *interp, Jsi_Obj* nobj, Jsi_Value *arr, int depth) {
     
-    int i, n = 0, len = Jsi_ObjGetLength(interp, arr->d.obj);
+    int i, n = 0, len = jsi_SizeOfArray(interp, arr->d.obj);
     if (len <= 0) return JSI_OK;
     Jsi_RC rc = JSI_OK;
-    int clen = Jsi_ObjGetLength(interp, nobj);
+    int clen = jsi_SizeOfArray(interp, nobj);
     for (i = 0; i < len && rc == JSI_OK; i++) {
         Jsi_Value *t = Jsi_ValueArrayIndex(interp, arr, i);
         if (t && depth>0 && Jsi_ValueIsArray(interp, t))
             rc = jsi_ArrayFlatSub(interp, nobj, t , depth-1);
         else if (!Jsi_ValueIsUndef(interp, t))
@@ -300,14 +302,11 @@ static Jsi_RC jsi_ArrayConcatCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
     Jsi_Value *va;
 
     obj = _this->d.obj;
     
     argc = Jsi_ValueGetLength(interp, args);
-    curlen = Jsi_ObjGetLength(interp, obj);
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
+    curlen = jsi_SizeOfArray(interp, obj);
     Jsi_ObjListifyArray(interp, obj);
    
     nobj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
     nsiz = obj->arrMaxSize;
     if (nsiz<=0) nsiz = 100;
@@ -375,14 +374,11 @@ static Jsi_RC jsi_ArrayMapCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_t
         return Jsi_LogError("expected function");
     sthis = Jsi_ValueArrayIndex(interp, args, 1);
     if (!sthis)
         sthis = nthis = Jsi_ValueNew1(interp);
     obj = _this->d.obj;
-    curlen = Jsi_ObjGetLength(interp, obj);    
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
+    curlen = jsi_SizeOfArray(interp, obj);    
     Jsi_ObjListifyArray(interp, obj);
     nobj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
     nsiz = obj->arrCnt;
     if (nsiz<=0) nsiz = 1;
     if (Jsi_ObjArraySizer(interp, nobj, nsiz) <= 0) {
@@ -437,14 +433,11 @@ static Jsi_RC jsi_ArrayFilterCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
         return Jsi_LogError("expected function");
     sthis = Jsi_ValueArrayIndex(interp, args, 1);
     if (!sthis)
         sthis = nthis = Jsi_ValueNew1(interp);
     obj = _this->d.obj;
-    curlen = Jsi_ObjGetLength(interp, obj);    
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
+    curlen = jsi_SizeOfArray(interp, obj);    
     Jsi_ObjListifyArray(interp, obj);
     nobj = Jsi_ObjNewType(interp, JSI_OT_ARRAY);
     nsiz = obj->arrCnt;
     if (nsiz<=0) nsiz = 1;
     if (Jsi_ObjArraySizer(interp, nobj, nsiz) <= 0) {
@@ -520,11 +513,10 @@ static Jsi_RC jsi_ArrayReverseCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
 static Jsi_RC jsi_ArrayForeachCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr) 
 {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj)) 
         return Jsi_LogError("expected array object");
     Jsi_Obj *obj;
-    int curlen;
     uint i;
     Jsi_Value *func, *vpargs;
 
     func = Jsi_ValueArrayIndex(interp, args, 0);
     if (!Jsi_ValueIsFunction(interp, func)) 
@@ -533,14 +525,10 @@ static Jsi_RC jsi_ArrayForeachCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
     Jsi_Value *nthis = NULL;
     if (!sthis)
         sthis = nthis = Jsi_ValueNew1(interp);
 
     obj = _this->d.obj;
-    curlen = Jsi_ObjGetLength(interp, obj);    
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
     Jsi_ObjListifyArray(interp, obj);
     Jsi_RC rc = JSI_OK;
     
     Jsi_Value *vobjs[3];
     Jsi_Func *fptr = func->d.obj->d.fobj->func;
@@ -564,11 +552,10 @@ static Jsi_RC jsi_ArrayForeachCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
 
 static Jsi_RC jsi_ArrayFindSubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr, int op) {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj)) 
         return Jsi_LogError("expected array");
     Jsi_Obj *obj;
-    int curlen;
     uint i;
     Jsi_RC rc = JSI_OK;
     Jsi_Value *func, *vpargs, *sthis = Jsi_ValueArrayIndex(interp, args, 1);
 
     func = Jsi_ValueArrayIndex(interp, args, 0);
@@ -577,14 +564,10 @@ static Jsi_RC jsi_ArrayFindSubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
     Jsi_Value *nthis = NULL;
     if (!sthis)
         sthis = nthis = Jsi_ValueNew1(interp);
 
     obj = _this->d.obj;
-    curlen = Jsi_ObjGetLength(interp, obj);    
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
     Jsi_ObjListifyArray(interp, obj);
     int fval = 0;
     Jsi_Value *nrPtr = Jsi_ValueNew1(interp);
     Jsi_Value *vobjs[3];
     Jsi_Func *fptr = func->d.obj->d.fobj->func;
@@ -626,23 +609,20 @@ static Jsi_RC jsi_ArrayFindSubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
 
 static Jsi_RC jsi_ArrayReduceSubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr, int op) {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj)) 
         return Jsi_LogError("expected array");
     Jsi_RC rc = JSI_OK;
-    int curlen, i;
+    int i;
     Jsi_Obj *obj;
     Jsi_Value *func, *vpargs, *ini = Jsi_ValueArrayIndex(interp, args, 1);
 
     func = Jsi_ValueArrayIndex(interp, args, 0);
     if (!Jsi_ValueIsFunction(interp, func)) 
         return Jsi_LogError("expected function");
 
     Jsi_Value *nrPtr = Jsi_ValueNew1(interp);
     obj = _this->d.obj;
-    curlen = Jsi_ObjGetLength(interp, obj);    
-    if (curlen < 0)
-        Jsi_ObjSetLength(interp, obj, 0);
     Jsi_ObjListifyArray(interp, obj);
     Jsi_Value *vobjs[4];
     int n, rev = (op==2);
     Jsi_Func *fptr = func->d.obj->d.fobj->func;
     int maa = (fptr->argnames?fptr->argnames->argCnt:0);
@@ -715,11 +695,11 @@ static Jsi_RC jsi_ArrayIndexSubCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Valu
 
     if (!seq) {
         goto bail;
     }
     
-    n = Jsi_ObjGetLength(interp, obj);    
+    n = jsi_SizeOfArray(interp, obj);    
     if (n == 0) {
         goto bail;
     }
     Jsi_Number nstart;
     if (op == 2) {
@@ -767,23 +747,22 @@ static Jsi_RC jsi_ArrayIncludesCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Valu
 }
 
 static Jsi_RC jsi_ArraySizeOfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr) {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj))
         return Jsi_LogError("expected array object");
-    int i = Jsi_ObjGetLength(interp, _this->d.obj);
+    int i = jsi_SizeOfArray(interp, _this->d.obj);
     Jsi_ValueMakeNumber(interp, ret, i);
     return JSI_OK;
 }
 
 static Jsi_RC jsi_ArrayShiftCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr) {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj))
         return Jsi_LogError("expected array object");
     Jsi_Value *v;
     Jsi_Obj *obj = _this->d.obj;
     Jsi_ObjListifyArray(interp, obj);
-    uint n = Jsi_ObjGetLength(interp, obj);
-    assert(n <= obj->arrCnt);
+    uint n = jsi_SizeOfArray(interp, obj);
     if (n<=0) {
         Jsi_ValueMakeUndef(interp, ret);
     } else {
         n--;
         v = obj->arr[0];
@@ -800,14 +779,11 @@ static Jsi_RC jsi_ArrayUnshiftCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj))
         return Jsi_LogError("expected array object");
 
     Jsi_Obj *obj = _this->d.obj;
     int argc = Jsi_ValueGetLength(interp, args);
-    int curlen = Jsi_ObjGetLength(interp, obj);
-    if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
-    }
+    int curlen = jsi_SizeOfArray(interp, obj);
     if (argc <= 0) {
         Jsi_ValueMakeNumber(interp, ret, 0);
         return JSI_OK;
     }
     Jsi_ObjListifyArray(interp, obj);
@@ -823,11 +799,11 @@ static Jsi_RC jsi_ArrayUnshiftCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
         obj->arr[i] = ov;
         Jsi_IncrRefCount(interp, ov);
     }
     Jsi_ObjSetLength(interp, obj, curlen+argc);
     
-    Jsi_ValueMakeNumber(interp, ret, Jsi_ObjGetLength(interp, obj));
+    Jsi_ValueMakeNumber(interp, ret, jsi_SizeOfArray(interp, obj));
     return JSI_OK;
 }
 
 static Jsi_RC jsi_ArrayFillCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,Jsi_Value **ret, Jsi_Func *funcPtr) {
     if (_this->vt != JSI_VT_OBJECT || !Jsi_ObjIsArray(interp, _this->d.obj)) 
@@ -837,11 +813,11 @@ static Jsi_RC jsi_ArrayFillCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_
     Jsi_Number nstart = 0, nend = 0; // TODO: merge with code in ArraySliceCmd.
     Jsi_Value *value = Jsi_ValueArrayIndex(interp, args, 0),
         *start = Jsi_ValueArrayIndex(interp, args, 1),
         *end = Jsi_ValueArrayIndex(interp, args, 2);
     Jsi_Obj *obj = _this->d.obj;
-    n = Jsi_ObjGetLength(interp, obj);
+    n = jsi_SizeOfArray(interp, obj);
 
     if (start && Jsi_GetNumberFromValue(interp, start, &nstart) == JSI_OK) {
         istart = (int)nstart;
         if (istart > n)
             goto bail;
@@ -900,11 +876,11 @@ static Jsi_RC jsi_ArraySliceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *
         *end = Jsi_ValueArrayIndex(interp, args, 1);
     if (!start) {
         goto bail;
     }
     obj = _this->d.obj;
-    n = Jsi_ObjGetLength(interp, obj);
+    n = jsi_SizeOfArray(interp, obj);
     if (Jsi_GetNumberFromValue(interp,start, &nstart) == JSI_OK) {
         istart = (int)nstart;
         if (istart > n)
             goto done;
         if (istart < 0)
@@ -1164,11 +1140,11 @@ static Jsi_RC jsi_ArraySpliceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
     start = Jsi_ValueArrayIndex(interp, args, 0);
     howmany = Jsi_ValueArrayIndex(interp, args, 1);
     argc = Jsi_ValueGetLength(interp, args);
     istart = 0;
     ilen = (argc>=2 ? argc - 2 : 0);
-    n = Jsi_ObjGetLength(interp, obj);
+    n = jsi_SizeOfArray(interp, obj);
     curlen = n;
     
     if (!start) {
         goto bail2;
     }
@@ -1200,11 +1176,11 @@ static Jsi_RC jsi_ArraySpliceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value
         if (rhowmany<0)
             goto bail;
     }
     
     if (curlen < 0) {
-        Jsi_ObjSetLength(interp, obj, 0);
+        Jsi_ObjSetLength(interp, obj, curlen=0);
     }
     Jsi_ObjListifyArray(interp, obj);
    
     Jsi_ObjArraySizer(interp, nobj, rhowmany);
 
diff --git a/src/jsiCmds.c b/src/jsiCmds.c
index bee9af2..e61d25a 100644
--- a/src/jsiCmds.c
+++ b/src/jsiCmds.c
@@ -4507,10 +4507,18 @@ done:
     Jsi_DSFree(&dStr);
     Jsi_DSFree(&nStr);
     return rc;
 }
 
+static Jsi_RC SysRunMainCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
+    Jsi_Value **ret, Jsi_Func *funcPtr)
+{
+    if (jsi_isMain(interp))
+        return SysRunModuleCmd(interp, args, _this, ret, funcPtr);
+    return JSI_OK;
+}
+
 static const char *jsi_FindHelpStr(const char *fstr, const char *key, Jsi_DString *dPtr) {
     if (!fstr) return "";
     Jsi_DSSetLength(dPtr, 0);
     const char *cp, *ce;
     int len = Jsi_Strlen(key);
@@ -4760,11 +4768,11 @@ static Jsi_CmdSpec infoCmds[] = {
 #endif
     { "executable", InfoExecutableCmd,  0,  0, "", .help="Return name of executable", .retType=(uint)JSI_TT_STRING },
     { "execZip",    InfoExecZipCmd,     0,  0, "", .help="If executing a .zip file, return file name", .retType=(uint)JSI_TT_STRING|JSI_TT_VOID },
     { "files",      InfoFilesCmd,       0,  0, "", .help="Return list of all sourced files", .retType=(uint)JSI_TT_ARRAY },
     { "funcs",      InfoFuncsCmd,       0,  1, "string|regexp|object=void", .help="Return details or list of matching functions", .retType=(uint)JSI_TT_ARRAY|JSI_TT_OBJECT },
-    { "locals",     InfoLocalsCmd,      0,  1, "varsOnly:boolean=void", .help="Return functions/vars inside local function", .retType=(uint)JSI_TT_OBJECT },
+    { "locals",     InfoLocalsCmd,      0,  1, "filter:boolean=void", .help="Return locals; use filter=true/false just vars/functions", .retType=(uint)JSI_TT_OBJECT },
     { "interp",     jsi_InterpInfo,     0,  1, "interp:userobj=void", .help="Return info on given or current interp", .retType=(uint)JSI_TT_OBJECT },
     { "isMain",     InfoIsMainCmd,      0,  0, "", .help="Return true if current script was the main script invoked from command-line", .retType=(uint)JSI_TT_BOOLEAN },
     { "keywords",   InfoKeywordsCmd,    0,  2, "isSql=false, name:string=void", .help="Return/lookup reserved keyword", .retType=(uint)JSI_TT_ARRAY|JSI_TT_BOOLEAN },
     { "level",      InfoLevelCmd,       0,  1, "level:number=void", .help="Return current level or details of a call-stack frame", .retType=(uint)JSI_TT_ARRAY|JSI_TT_OBJECT|JSI_TT_NUMBER, .flags=0, .info=FN_infolevel },
     { "lookup",     InfoLookupCmd,      1,  1, "name:string", .help="Given string name, lookup and return value, eg: function", .retType=(uint)JSI_TT_ANY },
@@ -4861,10 +4869,11 @@ static Jsi_CmdSpec sysCmds[] = {
     { "printf",     SysPrintfCmd,    1, -1, "format:string, ...", .help="Formatted output to stdout", .retType=(uint)JSI_TT_VOID, .flags=0 },
     { "provide",    SysProvideCmd,   0,  3, "name:string|function=void, version:number|string=1.0, opts:object|function=void", .help="Provide a package for use with require. Default is the file tail-rootname", .retType=(uint)JSI_TT_VOID },
     { "puts",       SysPutsCmd,      1, -1, "val, ...", .help="Output one or more values to stdout", .retType=(uint)JSI_TT_VOID, .flags=0, .info=FN_puts },
     { "quote",      SysQuoteCmd,     1,  1, "val:string", .help="Return quoted string", .retType=(uint)JSI_TT_STRING },
     { "require",    SysRequireCmd,   0,  3, "name:string=void, version:number|string=1, options:object=void", .help="Load/query packages", .retType=(uint)JSI_TT_NUMBER|JSI_TT_OBJECT|JSI_TT_ARRAY, .flags=0, .info=FN_require, .opts=jsiModuleOptions },
+    { "runMain",    SysRunMainCmd,   0,  2, "cmd:string|null|function=void, conf:array=undefined", .help="If isMain invokes runModule", .retType=(uint)JSI_TT_ANY, .flags=0},
     { "runModule",  SysRunModuleCmd, 0,  2, "cmd:string|null|function=void, conf:array=undefined", .help="Invoke named module. If name is empty, uses file basename. If isMain and no args givine parses console.args", .retType=(uint)JSI_TT_ANY, .flags=0},
     { "sleep",      SysSleepCmd,     0,  1, "secs:number=1.0",  .help="sleep for N milliseconds, minimum .001", .retType=(uint)JSI_TT_VOID },
 #ifndef JSI_OMIT_EVENT
     { "setInterval",setIntervalCmd,  2,  2, "callback:function, ms:number", .help="Setup recurring function to run every given millisecs", .retType=(uint)JSI_TT_NUMBER },
     { "setTimeout", setTimeoutCmd,   2,  2, "callback:function, ms:number", .help="Setup function to run after given millisecs", .retType=(uint)JSI_TT_NUMBER },
diff --git a/tests/locals.jsi b/tests/locals.jsi
new file mode 100644
index 0000000..3bd60e4
--- /dev/null
+++ b/tests/locals.jsi
@@ -0,0 +1,19 @@
+function foo() {
+
+
+  function bar() { }
+  var x = 1;
+;  Info.locals();
+;  Info.locals(false);
+;  Info.locals(true);
+}
+
+foo();
+
+/*
+=!EXPECTSTART!=
+Info.locals() ==> { bar:"function bar() {...}", x:1 }
+Info.locals(false) ==> { bar:"function bar() {...}" }
+Info.locals(true) ==> { x:1 }
+=!EXPECTEND!=
+*/
diff --git a/tools/protos.jsi b/tools/protos.jsi
index c27cb88..76d21d1 100755
--- a/tools/protos.jsi
+++ b/tools/protos.jsi
@@ -1,6 +1,6 @@
-//JSI Command Prototypes: version 3.0.4
+//JSI Command Prototypes: version 3.0.6
 throw("NOT EXECUTABLE: USE FILE IN GEANY EDITOR FOR CMD LINE COMPLETION + GOTO TAG");
 
 var Array = function(cmd,args) {};
 Array.prototype.concat = function(...):array {};
 Array.prototype.every = function(callback:function):any {};
@@ -138,11 +138,11 @@ Info.prototype.files = function():array {};
 Info.prototype.funcs = function(string|regexp|object=void):array|object {};
 Info.prototype.interp = function(interp:userobj=void):object {};
 Info.prototype.isMain = function():boolean {};
 Info.prototype.keywords = function(isSql=false, name:string=void):boolean|array {};
 Info.prototype.level = function(level:number=void):number|array|object {};
-Info.prototype.locals = function(varsOnly:boolean=void):object {};
+Info.prototype.locals = function(filter:boolean=void):object {};
 Info.prototype.lookup = function(name:string):any {};
 Info.prototype.methods = function(val:string|regexp):array|object {};
 Info.prototype.named = function(name:string=void):array|userobj {};
 Info.prototype.options = function(ctype:boolean=false):array {};
 Info.prototype.package = function(pkgName:string):object|null {};
@@ -301,10 +301,11 @@ System.prototype.parseOpts = function(self:object|userobj, options:object, conf:
 System.prototype.printf = function(format:string, ...):void {};
 System.prototype.provide = function(name:string|function=void, version:number|string=1.0, opts:object|function=void):void {};
 System.prototype.puts = function(val, ...):void {};
 System.prototype.quote = function(val:string):string {};
 System.prototype.require = function(name:string=void, version:number|string=1, options:object=void):number|array|object {};
+System.prototype.runMain = function(cmd:string|null|function=void, conf:array=undefined):any {};
 System.prototype.runModule = function(cmd:string|null|function=void, conf:array=undefined):any {};
 System.prototype.setInterval = function(callback:function, ms:number):number {};
 System.prototype.setTimeout = function(callback:function, ms:number):number {};
 System.prototype.sleep = function(secs:number=1.0):void {};
 System.prototype.source = function(val:string|array, options:object=void):any {};
@@ -392,10 +393,11 @@ var parseOpts = function(self:object|userobj, options:object, conf:object|null|u
 var printf = function(format:string, ...):void {};
 var provide = function(name:string|function=void, version:number|string=1.0, opts:object|function=void):void {};
 var puts = function(val, ...):void {};
 var quote = function(val:string):string {};
 var require = function(name:string=void, version:number|string=1, options:object=void):number|array|object {};
+var runMain = function(cmd:string|null|function=void, conf:array=undefined):any {};
 var runModule = function(cmd:string|null|function=void, conf:array=undefined):any {};
 var setInterval = function(callback:function, ms:number):number {};
 var setTimeout = function(callback:function, ms:number):number {};
 var sleep = function(secs:number=1.0):void {};
 var source = function(val:string|array, options:object=void):any {};
diff --git a/www/reference.wiki b/www/reference.wiki
index a152c5c..2b11595 100644
--- a/www/reference.wiki
+++ b/www/reference.wiki
@@ -550,11 +550,11 @@ Otherwise second argument must be a set of options.</td></tr>
 <tr><td>isMain</td><td>isMain():boolean </td><td>Return true if current script was the main script invoked from command-line.</td></tr>
 <tr><td>keywords</td><td>keywords(isSql=false, name:string=void):boolean|array </td><td>Return/lookup reserved keyword.</td></tr>
 <tr><td>level</td><td>level(level:number=void):number|array|object </td><td>Return current level or details of a call-stack frame. With no arg, returns the number of the current stack frame level.
 Otherwise returns details on the specified level.
 The topmost level is 1, and 0 is the current level, and a negative level translates as relative to the current level.</td></tr>
-<tr><td>locals</td><td>locals(varsOnly:boolean=void):object </td><td>Return functions/vars inside local function.</td></tr>
+<tr><td>locals</td><td>locals(filter:boolean=void):object </td><td>Return locals; use filter=true/false just vars/functions.</td></tr>
 <tr><td>lookup</td><td>lookup(name:string) </td><td>Given string name, lookup and return value, eg: function.</td></tr>
 <tr><td>methods</td><td>methods(val:string|regexp):array|object </td><td>Return functions and commands.</td></tr>
 <tr><td>named</td><td>named(name:string=void):array|userobj </td><td>Returns command names for builtin Objects, eg: 'File', 'Interp', sub-Object names, or the named object.</td></tr>
 <tr><td>options</td><td>options(ctype:boolean=false):array </td><td>Return Option type name, or with true the C type.</td></tr>
 <tr><td>package</td><td>package(pkgName:string):object|null </td><td>Return info about provided package if exists, else null.</td></tr>
@@ -1359,10 +1359,11 @@ By default, returns the string output, unless the 'bg', 'inputStr', 'retCode' or
 <tr><td>require</td><td>require(name:string=void, version:number|string=1, <a href='#System.requireOptions'>options</a>:object=void):number|array|object </td><td>Load/query packages. With no arguments, returns the list of all loaded packages.
 With one argument, loads the package (if necessary) and returns its version.
 With two arguments, returns object containing: version, loadFile, func.
 A third argument sets options for package or module.
 Note an error is thrown if requested version is greater than actual version.</td></tr>
+<tr><td>runMain</td><td>runMain(cmd:string|null|function=void, conf:array=undefined) </td><td>If isMain invokes runModule.</td></tr>
 <tr><td>runModule</td><td>runModule(cmd:string|null|function=void, conf:array=undefined) </td><td>Invoke named module. If name is empty, uses file basename. If isMain and no args givine parses console.args.</td></tr>
 <tr><td>setInterval</td><td>setInterval(callback:function, ms:number):number </td><td>Setup recurring function to run every given millisecs.</td></tr>
 <tr><td>setTimeout</td><td>setTimeout(callback:function, ms:number):number </td><td>Setup function to run after given millisecs.</td></tr>
 <tr><td>sleep</td><td>sleep(secs:number=1.0):void </td><td>sleep for N milliseconds, minimum .001.</td></tr>
 <tr><td>source</td><td>source(val:string|array, <a href='#System.sourceOptions'>options</a>:object=void) </td><td>Load and evaluate source files: trailing '/' appends PARENTDIR.jsi.</td></tr>

Vulnerable code: <tr><td>locals</td><td>locals(varsOnly:boolean=void):object </td><td>Return functions/vars inside local function.</td></tr>
#define JSI_VERSION_RELEASE 4
    int curlen = Jsi_ObjGetLength(interp, obj);
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    
    Jsi_ValueMakeNumber(interp, ret, Jsi_ObjGetLength(interp, obj));
    int i = Jsi_ObjGetLength(interp, obj) - 1;
    curlen = Jsi_ObjGetLength(interp, _this->d.obj);
    if (0 == (argc=Jsi_ObjGetLength(interp, _this->d.obj))) {
    int len = Jsi_ObjGetLength(interp, v->d.obj);
    int i, n = 0, len = Jsi_ObjGetLength(interp, arr->d.obj);
    int clen = Jsi_ObjGetLength(interp, nobj);
    curlen = Jsi_ObjGetLength(interp, obj);
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    curlen = Jsi_ObjGetLength(interp, obj);    
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    curlen = Jsi_ObjGetLength(interp, obj);    
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    int curlen;
    curlen = Jsi_ObjGetLength(interp, obj);    
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    int curlen;
    curlen = Jsi_ObjGetLength(interp, obj);    
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    int curlen, i;
    curlen = Jsi_ObjGetLength(interp, obj);    
    if (curlen < 0)
        Jsi_ObjSetLength(interp, obj, 0);
    n = Jsi_ObjGetLength(interp, obj);    
    int i = Jsi_ObjGetLength(interp, _this->d.obj);
    uint n = Jsi_ObjGetLength(interp, obj);
    assert(n <= obj->arrCnt);
    int curlen = Jsi_ObjGetLength(interp, obj);
    if (curlen < 0) {
        Jsi_ObjSetLength(interp, obj, 0);
    }
    Jsi_ValueMakeNumber(interp, ret, Jsi_ObjGetLength(interp, obj));
    n = Jsi_ObjGetLength(interp, obj);
    n = Jsi_ObjGetLength(interp, obj);
    n = Jsi_ObjGetLength(interp, obj);
        Jsi_ObjSetLength(interp, obj, 0);
    { "locals",     InfoLocalsCmd,      0,  1, "varsOnly:boolean=void", .help="Return functions/vars inside local function", .retType=(uint)JSI_TT_OBJECT },
//JSI Command Prototypes: version 3.0.4
Info.prototype.locals = function(varsOnly:boolean=void):object {};
<tr><td>locals</td><td>locals(varsOnly:boolean=void):object </td><td>Return functions/vars inside local function.</td></tr>
Fixed code: <tr><td>locals</td><td>locals(filter:boolean=void):object </td><td>Return locals; use filter=true/false just vars/functions.</td></tr>
<tr><td>runMain</td><td>runMain(cmd:string|null|function=void, conf:array=undefined) </td><td>If isMain invokes runModule.</td></tr>
#define JSI_VERSION_RELEASE 6
static uint jsi_SizeOfArray(Jsi_Interp *interp, Jsi_Obj *obj) {
    if (!obj || !obj->arr)
        return 0;
    return obj->arrCnt;
}

    int curlen = jsi_SizeOfArray(interp, obj);    
    Jsi_ValueMakeNumber(interp, ret, jsi_SizeOfArray(interp, obj));
    int i = jsi_SizeOfArray(interp, obj) - 1;
    curlen = jsi_SizeOfArray(interp, _this->d.obj);
    if (0 == (argc=jsi_SizeOfArray(interp, _this->d.obj))) {
    int len = jsi_SizeOfArray(interp, v->d.obj);
    int i, n = 0, len = jsi_SizeOfArray(interp, arr->d.obj);
    int clen = jsi_SizeOfArray(interp, nobj);
    curlen = jsi_SizeOfArray(interp, obj);
    curlen = jsi_SizeOfArray(interp, obj);    
    curlen = jsi_SizeOfArray(interp, obj);    
    int i;
    n = jsi_SizeOfArray(interp, obj);    
    int i = jsi_SizeOfArray(interp, _this->d.obj);
    uint n = jsi_SizeOfArray(interp, obj);
    int curlen = jsi_SizeOfArray(interp, obj);
    Jsi_ValueMakeNumber(interp, ret, jsi_SizeOfArray(interp, obj));
    n = jsi_SizeOfArray(interp, obj);
    n = jsi_SizeOfArray(interp, obj);
    n = jsi_SizeOfArray(interp, obj);
        Jsi_ObjSetLength(interp, obj, curlen=0);
static Jsi_RC SysRunMainCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (jsi_isMain(interp))
        return SysRunModuleCmd(interp, args, _this, ret, funcPtr);
    return JSI_OK;
}

    { "locals",     InfoLocalsCmd,      0,  1, "filter:boolean=void", .help="Return locals; use filter=true/false just vars/functions", .retType=(uint)JSI_TT_OBJECT },
    { "runMain",    SysRunMainCmd,   0,  2, "cmd:string|null|function=void, conf:array=undefined", .help="If isMain invokes runModule", .retType=(uint)JSI_TT_ANY, .flags=0},
function foo() {


  function bar() { }
  var x = 1;
;  Info.locals();
;  Info.locals(false);
;  Info.locals(true);
}

foo();

/*
=!EXPECTSTART!=
Info.locals() ==> { bar:"function bar() {...}", x:1 }
Info.locals(false) ==> { bar:"function bar() {...}" }
Info.locals(true) ==> { x:1 }
=!EXPECTEND!=
*/
//JSI Command Prototypes: version 3.0.6
Info.prototype.locals = function(filter:boolean=void):object {};
System.prototype.runMain = function(cmd:string|null|function=void, conf:array=undefined):any {};
var runMain = function(cmd:string|null|function=void, conf:array=undefined):any {};
<tr><td>locals</td><td>locals(filter:boolean=void):object </td><td>Return locals; use filter=true/false just vars/functions.</td></tr>
<tr><td>runMain</td><td>runMain(cmd:string|null|function=void, conf:array=undefined) </td><td>If isMain invokes runModule.</td></tr>