#include "jsi.h" #include "jscompile.h" #include "jsvalue.h" #include "jsrun.h" #include "utf.h" static void jsR_run(js_State *J, js_Function *F); /* Push values on stack */ #define STACK (J->stack) #define TOP (J->top) #define BOT (J->bot) static void js_stackoverflow(js_State *J) { STACK[TOP].type = JS_TLITSTR; STACK[TOP].u.litstr = "stack overflow"; ++TOP; js_throw(J); } static void js_outofmemory(js_State *J) { STACK[TOP].type = JS_TLITSTR; STACK[TOP].u.litstr = "out of memory"; ++TOP; js_throw(J); } void *js_malloc(js_State *J, int size) { void *ptr = J->alloc(J->actx, NULL, size); if (!ptr) js_outofmemory(J); return ptr; } void *js_realloc(js_State *J, void *ptr, int size) { ptr = J->alloc(J->actx, ptr, size); if (!ptr) js_outofmemory(J); return ptr; } char *js_strdup(js_State *J, const char *s) { int n = strlen(s) + 1; char *p = js_malloc(J, n); memcpy(p, s, n); return p; } void js_free(js_State *J, void *ptr) { J->alloc(J->actx, ptr, 0); } js_String *jsV_newmemstring(js_State *J, const char *s, int n) { js_String *v = js_malloc(J, soffsetof(js_String, p) + n + 1); memcpy(v->p, s, n); v->p[n] = 0; v->gcmark = 0; v->gcnext = J->gcstr; J->gcstr = v; ++J->gccounter; return v; } #define CHECKSTACK(n) if (TOP + n >= JS_STACKSIZE) js_stackoverflow(J) void js_pushvalue(js_State *J, js_Value v) { CHECKSTACK(1); STACK[TOP] = v; ++TOP; } void js_pushundefined(js_State *J) { CHECKSTACK(1); STACK[TOP].type = JS_TUNDEFINED; ++TOP; } void js_pushnull(js_State *J) { CHECKSTACK(1); STACK[TOP].type = JS_TNULL; ++TOP; } void js_pushboolean(js_State *J, int v) { CHECKSTACK(1); STACK[TOP].type = JS_TBOOLEAN; STACK[TOP].u.boolean = !!v; ++TOP; } void js_pushnumber(js_State *J, double v) { CHECKSTACK(1); STACK[TOP].type = JS_TNUMBER; STACK[TOP].u.number = v; ++TOP; } void js_pushstring(js_State *J, const char *v) { int n = strlen(v); CHECKSTACK(1); if (n <= soffsetof(js_Value, type)) { char *s = STACK[TOP].u.shrstr; while (n--) *s++ = *v++; *s = 0; STACK[TOP].type = JS_TSHRSTR; } else { STACK[TOP].type = JS_TMEMSTR; STACK[TOP].u.memstr = jsV_newmemstring(J, v, n); } ++TOP; } void js_pushlstring(js_State *J, const char *v, int n) { CHECKSTACK(1); if (n <= soffsetof(js_Value, type)) { char *s = STACK[TOP].u.shrstr; while (n--) *s++ = *v++; *s = 0; STACK[TOP].type = JS_TSHRSTR; } else { STACK[TOP].type = JS_TMEMSTR; STACK[TOP].u.memstr = jsV_newmemstring(J, v, n); } ++TOP; } void js_pushliteral(js_State *J, const char *v) { CHECKSTACK(1); STACK[TOP].type = JS_TLITSTR; STACK[TOP].u.litstr = v; ++TOP; } void js_pushobject(js_State *J, js_Object *v) { CHECKSTACK(1); STACK[TOP].type = JS_TOBJECT; STACK[TOP].u.object = v; ++TOP; } void js_pushglobal(js_State *J) { js_pushobject(J, J->G); } void js_currentfunction(js_State *J) { CHECKSTACK(1); STACK[TOP] = STACK[BOT-1]; ++TOP; } /* Read values from stack */ static js_Value *stackidx(js_State *J, int idx) { static js_Value undefined = { {0}, {0}, JS_TUNDEFINED }; idx = idx < 0 ? TOP + idx : BOT + idx; if (idx < 0 || idx >= TOP) return &undefined; return STACK + idx; } js_Value *js_tovalue(js_State *J, int idx) { return stackidx(J, idx); } int js_isdefined(js_State *J, int idx) { return stackidx(J, idx)->type != JS_TUNDEFINED; } int js_isundefined(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TUNDEFINED; } int js_isnull(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TNULL; } int js_isboolean(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TBOOLEAN; } int js_isnumber(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TNUMBER; } int js_isstring(js_State *J, int idx) { enum js_Type t = stackidx(J, idx)->type; return t == JS_TSHRSTR || t == JS_TLITSTR || t == JS_TMEMSTR; } int js_isprimitive(js_State *J, int idx) { return stackidx(J, idx)->type != JS_TOBJECT; } int js_isobject(js_State *J, int idx) { return stackidx(J, idx)->type == JS_TOBJECT; } int js_iscoercible(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->type != JS_TUNDEFINED && v->type != JS_TNULL; } int js_iscallable(js_State *J, int idx) { js_Value *v = stackidx(J, idx); if (v->type == JS_TOBJECT) return v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CSCRIPT || v->u.object->type == JS_CEVAL || v->u.object->type == JS_CCFUNCTION; return 0; } int js_isarray(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->type == JS_TOBJECT && v->u.object->type == JS_CARRAY; } int js_isregexp(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->type == JS_TOBJECT && v->u.object->type == JS_CREGEXP; } int js_isuserdata(js_State *J, int idx, const char *tag) { js_Value *v = stackidx(J, idx); if (v->type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA) return !strcmp(tag, v->u.object->u.user.tag); return 0; } int js_iserror(js_State *J, int idx) { js_Value *v = stackidx(J, idx); return v->type == JS_TOBJECT && v->u.object->type == JS_CERROR; } const char *js_typeof(js_State *J, int idx) { js_Value *v = stackidx(J, idx); switch (v->type) { default: case JS_TSHRSTR: return "string"; case JS_TUNDEFINED: return "undefined"; case JS_TNULL: return "object"; case JS_TBOOLEAN: return "boolean"; case JS_TNUMBER: return "number"; case JS_TLITSTR: return "string"; case JS_TMEMSTR: return "string"; case JS_TOBJECT: if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION) return "function"; return "object"; } } int js_toboolean(js_State *J, int idx) { return jsV_toboolean(J, stackidx(J, idx)); } double js_tonumber(js_State *J, int idx) { return jsV_tonumber(J, stackidx(J, idx)); } int js_tointeger(js_State *J, int idx) { return jsV_numbertointeger(jsV_tonumber(J, stackidx(J, idx))); } int js_toint32(js_State *J, int idx) { return jsV_numbertoint32(jsV_tonumber(J, stackidx(J, idx))); } unsigned int js_touint32(js_State *J, int idx) { return jsV_numbertouint32(jsV_tonumber(J, stackidx(J, idx))); } short js_toint16(js_State *J, int idx) { return jsV_numbertoint16(jsV_tonumber(J, stackidx(J, idx))); } unsigned short js_touint16(js_State *J, int idx) { return jsV_numbertouint16(jsV_tonumber(J, stackidx(J, idx))); } const char *js_tostring(js_State *J, int idx) { return jsV_tostring(J, stackidx(J, idx)); } js_Object *js_toobject(js_State *J, int idx) { return jsV_toobject(J, stackidx(J, idx)); } void js_toprimitive(js_State *J, int idx, int hint) { jsV_toprimitive(J, stackidx(J, idx), hint); } js_Regexp *js_toregexp(js_State *J, int idx) { js_Value *v = stackidx(J, idx); if (v->type == JS_TOBJECT && v->u.object->type == JS_CREGEXP) return &v->u.object->u.r; js_typeerror(J, "not a regexp"); } void *js_touserdata(js_State *J, int idx, const char *tag) { js_Value *v = stackidx(J, idx); if (v->type == JS_TOBJECT && v->u.object->type == JS_CUSERDATA) if (!strcmp(tag, v->u.object->u.user.tag)) return v->u.object->u.user.data; js_typeerror(J, "not a %s", tag); } static js_Object *jsR_tofunction(js_State *J, int idx) { js_Value *v = stackidx(J, idx); if (v->type == JS_TUNDEFINED || v->type == JS_TNULL) return NULL; if (v->type == JS_TOBJECT) if (v->u.object->type == JS_CFUNCTION || v->u.object->type == JS_CCFUNCTION) return v->u.object; js_typeerror(J, "not a function"); } /* Stack manipulation */ int js_gettop(js_State *J) { return TOP - BOT; } void js_pop(js_State *J, int n) { TOP -= n; if (TOP < BOT) { TOP = BOT; js_error(J, "stack underflow!"); } } void js_remove(js_State *J, int idx) { idx = idx < 0 ? TOP + idx : BOT + idx; if (idx < BOT || idx >= TOP) js_error(J, "stack error!"); for (;idx < TOP - 1; ++idx) STACK[idx] = STACK[idx+1]; --TOP; } void js_insert(js_State *J, int idx) { js_error(J, "not implemented yet"); } void js_replace(js_State* J, int idx) { idx = idx < 0 ? TOP + idx : BOT + idx; if (idx < BOT || idx >= TOP) js_error(J, "stack error!"); STACK[idx] = STACK[--TOP]; } void js_copy(js_State *J, int idx) { CHECKSTACK(1); STACK[TOP] = *stackidx(J, idx); ++TOP; } void js_dup(js_State *J) { CHECKSTACK(1); STACK[TOP] = STACK[TOP-1]; ++TOP; } void js_dup2(js_State *J) { CHECKSTACK(2); STACK[TOP] = STACK[TOP-2]; STACK[TOP+1] = STACK[TOP-1]; TOP += 2; } void js_rot2(js_State *J) { /* A B -> B A */ js_Value tmp = STACK[TOP-1]; /* A B (B) */ STACK[TOP-1] = STACK[TOP-2]; /* A A */ STACK[TOP-2] = tmp; /* B A */ } void js_rot3(js_State *J) { /* A B C -> C A B */ js_Value tmp = STACK[TOP-1]; /* A B C (C) */ STACK[TOP-1] = STACK[TOP-2]; /* A B B */ STACK[TOP-2] = STACK[TOP-3]; /* A A B */ STACK[TOP-3] = tmp; /* C A B */ } void js_rot4(js_State *J) { /* A B C D -> D A B C */ js_Value tmp = STACK[TOP-1]; /* A B C D (D) */ STACK[TOP-1] = STACK[TOP-2]; /* A B C C */ STACK[TOP-2] = STACK[TOP-3]; /* A B B C */ STACK[TOP-3] = STACK[TOP-4]; /* A A B C */ STACK[TOP-4] = tmp; /* D A B C */ } void js_rot2pop1(js_State *J) { /* A B -> B */ STACK[TOP-2] = STACK[TOP-1]; --TOP; } void js_rot3pop2(js_State *J) { /* A B C -> C */ STACK[TOP-3] = STACK[TOP-1]; TOP -= 2; } void js_rot(js_State *J, int n) { int i; js_Value tmp = STACK[TOP-1]; for (i = 1; i < n; ++i) STACK[TOP-i] = STACK[TOP-i-1]; STACK[TOP-i] = tmp; } /* Property access that takes care of attributes and getters/setters */ int js_isarrayindex(js_State *J, const char *p, int *idx) { int n = 0; /* check for empty string */ if (p[0] == 0) return 0; /* check for '0' and integers with leading zero */ if (p[0] == '0') return (p[1] == 0) ? *idx = 0, 1 : 0; while (*p) { int c = *p++; if (c >= '0' && c <= '9') { if (n >= INT_MAX / 10) return 0; n = n * 10 + (c - '0'); } else { return 0; } } return *idx = n, 1; } static void js_pushrune(js_State *J, Rune rune) { char buf[UTFmax + 1]; if (rune >= 0) { buf[runetochar(buf, &rune)] = 0; js_pushstring(J, buf); } else { js_pushundefined(J); } } static int jsR_hasproperty(js_State *J, js_Object *obj, const char *name) { js_Property *ref; int k; if (obj->type == JS_CARRAY) { if (!strcmp(name, "length")) { js_pushnumber(J, obj->u.a.length); return 1; } } else if (obj->type == JS_CSTRING) { if (!strcmp(name, "length")) { js_pushnumber(J, obj->u.s.length); return 1; } if (js_isarrayindex(J, name, &k)) { if (k >= 0 && k < obj->u.s.length) { js_pushrune(J, js_runeat(J, obj->u.s.string, k)); return 1; } } } else if (obj->type == JS_CREGEXP) { if (!strcmp(name, "source")) { js_pushliteral(J, obj->u.r.source); return 1; } if (!strcmp(name, "global")) { js_pushboolean(J, obj->u.r.flags & JS_REGEXP_G); return 1; } if (!strcmp(name, "ignoreCase")) { js_pushboolean(J, obj->u.r.flags & JS_REGEXP_I); return 1; } if (!strcmp(name, "multiline")) { js_pushboolean(J, obj->u.r.flags & JS_REGEXP_M); return 1; } if (!strcmp(name, "lastIndex")) { js_pushnumber(J, obj->u.r.last); return 1; } } else if (obj->type == JS_CUSERDATA) { if (obj->u.user.has && obj->u.user.has(J, obj->u.user.data, name)) return 1; } ref = jsV_getproperty(J, obj, name); if (ref) { if (ref->getter) { js_pushobject(J, ref->getter); js_pushobject(J, obj); js_call(J, 0); } else { js_pushvalue(J, ref->value); } return 1; } return 0; } static void jsR_getproperty(js_State *J, js_Object *obj, const char *name) { if (!jsR_hasproperty(J, obj, name)) js_pushundefined(J); } static void jsR_setproperty(js_State *J, js_Object *obj, const char *name) { js_Value *value = stackidx(J, -1); js_Property *ref; int k; int own; if (obj->type == JS_CARRAY) { if (!strcmp(name, "length")) { double rawlen = jsV_tonumber(J, value); int newlen = jsV_numbertointeger(rawlen); if (newlen != rawlen || newlen < 0) js_rangeerror(J, "invalid array length"); jsV_resizearray(J, obj, newlen); return; } if (js_isarrayindex(J, name, &k)) if (k >= obj->u.a.length) obj->u.a.length = k + 1; } else if (obj->type == JS_CSTRING) { if (!strcmp(name, "length")) goto readonly; if (js_isarrayindex(J, name, &k)) if (k >= 0 && k < obj->u.s.length) goto readonly; } else if (obj->type == JS_CREGEXP) { if (!strcmp(name, "source")) goto readonly; if (!strcmp(name, "global")) goto readonly; if (!strcmp(name, "ignoreCase")) goto readonly; if (!strcmp(name, "multiline")) goto readonly; if (!strcmp(name, "lastIndex")) { obj->u.r.last = jsV_tointeger(J, value); return; } } else if (obj->type == JS_CUSERDATA) { if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name)) return; } /* First try to find a setter in prototype chain */ ref = jsV_getpropertyx(J, obj, name, &own); if (ref) { if (ref->setter) { js_pushobject(J, ref->setter); js_pushobject(J, obj); js_pushvalue(J, *value); js_call(J, 1); js_pop(J, 1); return; } else { if (J->strict) if (ref->getter) js_typeerror(J, "setting property '%s' that only has a getter", name); if (ref->atts & JS_READONLY) goto readonly; } } /* Property not found on this object, so create one */ if (!ref || !own) ref = jsV_setproperty(J, obj, name); if (ref) { if (!(ref->atts & JS_READONLY)) ref->value = *value; else goto readonly; } return; readonly: if (J->strict) js_typeerror(J, "'%s' is read-only", name); } static void jsR_defproperty(js_State *J, js_Object *obj, const char *name, int atts, js_Value *value, js_Object *getter, js_Object *setter) { js_Property *ref; int k; if (obj->type == JS_CARRAY) { if (!strcmp(name, "length")) goto readonly; } else if (obj->type == JS_CSTRING) { if (!strcmp(name, "length")) goto readonly; if (js_isarrayindex(J, name, &k)) if (k >= 0 && k < obj->u.s.length) goto readonly; } else if (obj->type == JS_CREGEXP) { if (!strcmp(name, "source")) goto readonly; if (!strcmp(name, "global")) goto readonly; if (!strcmp(name, "ignoreCase")) goto readonly; if (!strcmp(name, "multiline")) goto readonly; if (!strcmp(name, "lastIndex")) goto readonly; } else if (obj->type == JS_CUSERDATA) { if (obj->u.user.put && obj->u.user.put(J, obj->u.user.data, name)) return; } ref = jsV_setproperty(J, obj, name); if (ref) { if (value) { if (!(ref->atts & JS_READONLY)) ref->value = *value; else if (J->strict) js_typeerror(J, "'%s' is read-only", name); } if (getter) { if (!(ref->atts & JS_DONTCONF)) ref->getter = getter; else if (J->strict) js_typeerror(J, "'%s' is non-configurable", name); } if (setter) { if (!(ref->atts & JS_DONTCONF)) ref->setter = setter; else if (J->strict) js_typeerror(J, "'%s' is non-configurable", name); } ref->atts |= atts; } return; readonly: if (J->strict) js_typeerror(J, "'%s' is read-only or non-configurable", name); } static int jsR_delproperty(js_State *J, js_Object *obj, const char *name) { js_Property *ref; int k; if (obj->type == JS_CARRAY) { if (!strcmp(name, "length")) goto dontconf; } else if (obj->type == JS_CSTRING) { if (!strcmp(name, "length")) goto dontconf; if (js_isarrayindex(J, name, &k)) if (k >= 0 && k < obj->u.s.length) goto dontconf; } else if (obj->type == JS_CREGEXP) { if (!strcmp(name, "source")) goto dontconf; if (!strcmp(name, "global")) goto dontconf; if (!strcmp(name, "ignoreCase")) goto dontconf; if (!strcmp(name, "multiline")) goto dontconf; if (!strcmp(name, "lastIndex")) goto dontconf; } else if (obj->type == JS_CUSERDATA) { if (obj->u.user.delete && obj->u.user.delete(J, obj->u.user.data, name)) return 1; } ref = jsV_getownproperty(J, obj, name); if (ref) { if (ref->atts & JS_DONTCONF) goto dontconf; jsV_delproperty(J, obj, name); } return 1; dontconf: if (J->strict) js_typeerror(J, "'%s' is non-configurable", name); return 0; } /* Registry, global and object property accessors */ const char *js_ref(js_State *J) { js_Value *v = stackidx(J, -1); const char *s; char buf[32]; switch (v->type) { case JS_TUNDEFINED: s = "_Undefined"; break; case JS_TNULL: s = "_Null"; break; case JS_TBOOLEAN: s = v->u.boolean ? "_True" : "_False"; break; case JS_TOBJECT: sprintf(buf, "%p", (void*)v->u.object); s = js_intern(J, buf); break; default: sprintf(buf, "%d", J->nextref++); s = js_intern(J, buf); break; } js_setregistry(J, s); return s; } void js_unref(js_State *J, const char *ref) { js_delregistry(J, ref); } void js_getregistry(js_State *J, const char *name) { jsR_getproperty(J, J->R, name); } void js_setregistry(js_State *J, const char *name) { jsR_setproperty(J, J->R, name); js_pop(J, 1); } void js_delregistry(js_State *J, const char *name) { jsR_delproperty(J, J->R, name); } void js_getglobal(js_State *J, const char *name) { jsR_getproperty(J, J->G, name); } void js_setglobal(js_State *J, const char *name) { jsR_setproperty(J, J->G, name); js_pop(J, 1); } void js_defglobal(js_State *J, const char *name, int atts) { jsR_defproperty(J, J->G, name, atts, stackidx(J, -1), NULL, NULL); js_pop(J, 1); } void js_delglobal(js_State *J, const char *name) { jsR_delproperty(J, J->G, name); } void js_getproperty(js_State *J, int idx, const char *name) { jsR_getproperty(J, js_toobject(J, idx), name); } void js_setproperty(js_State *J, int idx, const char *name) { jsR_setproperty(J, js_toobject(J, idx), name); js_pop(J, 1); } void js_defproperty(js_State *J, int idx, const char *name, int atts) { jsR_defproperty(J, js_toobject(J, idx), name, atts, stackidx(J, -1), NULL, NULL); js_pop(J, 1); } void js_delproperty(js_State *J, int idx, const char *name) { jsR_delproperty(J, js_toobject(J, idx), name); } void js_defaccessor(js_State *J, int idx, const char *name, int atts) { jsR_defproperty(J, js_toobject(J, idx), name, atts, NULL, jsR_tofunction(J, -2), jsR_tofunction(J, -1)); js_pop(J, 2); } int js_hasproperty(js_State *J, int idx, const char *name) { return jsR_hasproperty(J, js_toobject(J, idx), name); } /* Iterator */ void js_pushiterator(js_State *J, int idx, int own) { js_pushobject(J, jsV_newiterator(J, js_toobject(J, idx), own)); } const char *js_nextiterator(js_State *J, int idx) { return jsV_nextiterator(J, js_toobject(J, idx)); } /* Environment records */ js_Environment *jsR_newenvironment(js_State *J, js_Object *vars, js_Environment *outer) { js_Environment *E = js_malloc(J, sizeof *E); E->gcmark = 0; E->gcnext = J->gcenv; J->gcenv = E; ++J->gccounter; E->outer = outer; E->variables = vars; return E; } static void js_initvar(js_State *J, const char *name, int idx) { jsR_defproperty(J, J->E->variables, name, JS_DONTENUM | JS_DONTCONF, stackidx(J, idx), NULL, NULL); } static int js_hasvar(js_State *J, const char *name) { js_Environment *E = J->E; do { js_Property *ref = jsV_getproperty(J, E->variables, name); if (ref) { if (ref->getter) { js_pushobject(J, ref->getter); js_pushobject(J, E->variables); js_call(J, 0); } else { js_pushvalue(J, ref->value); } return 1; } E = E->outer; } while (E); return 0; } static void js_setvar(js_State *J, const char *name) { js_Environment *E = J->E; do { js_Property *ref = jsV_getproperty(J, E->variables, name); if (ref) { if (ref->setter) { js_pushobject(J, ref->setter); js_pushobject(J, E->variables); js_copy(J, -3); js_call(J, 1); js_pop(J, 1); return; } if (!(ref->atts & JS_READONLY)) ref->value = *stackidx(J, -1); else if (J->strict) js_typeerror(J, "'%s' is read-only", name); return; } E = E->outer; } while (E); if (J->strict) js_referenceerror(J, "assignment to undeclared variable '%s'", name); jsR_setproperty(J, J->G, name); } static int js_delvar(js_State *J, const char *name) { js_Environment *E = J->E; do { js_Property *ref = jsV_getownproperty(J, E->variables, name); if (ref) { if (ref->atts & JS_DONTCONF) { if (J->strict) js_typeerror(J, "'%s' is non-configurable", name); return 0; } jsV_delproperty(J, E->variables, name); return 1; } E = E->outer; } while (E); return jsR_delproperty(J, J->G, name); } /* Function calls */ static void jsR_savescope(js_State *J, js_Environment *newE) { if (J->envtop + 1 >= JS_ENVLIMIT) js_stackoverflow(J); J->envstack[J->envtop++] = J->E; J->E = newE; } static void jsR_restorescope(js_State *J) { J->E = J->envstack[--J->envtop]; } static void jsR_calllwfunction(js_State *J, int n, js_Function *F, js_Environment *scope) { js_Value v; int i; jsR_savescope(J, scope); if (n > F->numparams) { js_pop(J, n - F->numparams); n = F->numparams; } for (i = n; i < F->varlen; ++i) js_pushundefined(J); jsR_run(J, F); v = *stackidx(J, -1); TOP = --BOT; /* clear stack */ js_pushvalue(J, v); jsR_restorescope(J); } static void jsR_callfunction(js_State *J, int n, js_Function *F, js_Environment *scope) { js_Value v; int i; scope = jsR_newenvironment(J, jsV_newobject(J, JS_COBJECT, NULL), scope); jsR_savescope(J, scope); if (F->arguments) { js_newarguments(J); if (!J->strict) { js_currentfunction(J); js_defproperty(J, -2, "callee", JS_DONTENUM); } js_pushnumber(J, n); js_defproperty(J, -2, "length", JS_DONTENUM); for (i = 0; i < n; ++i) { js_copy(J, i + 1); js_setindex(J, -2, i); } js_initvar(J, "arguments", -1); js_pop(J, 1); } for (i = 0; i < n && i < F->numparams; ++i) js_initvar(J, F->vartab[i], i + 1); js_pop(J, n); for (; i < F->varlen; ++i) { js_pushundefined(J); js_initvar(J, F->vartab[i], -1); js_pop(J, 1); } jsR_run(J, F); v = *stackidx(J, -1); TOP = --BOT; /* clear stack */ js_pushvalue(J, v); jsR_restorescope(J); } static void jsR_calleval(js_State *J, int n, js_Function *F, js_Environment *scope) { js_Value v; int i; scope = jsR_newenvironment(J, jsV_newobject(J, JS_COBJECT, NULL), scope); jsR_savescope(J, scope); /* scripts take no arguments */ js_pop(J, n); for (i = 0; i < F->varlen; ++i) { js_pushundefined(J); js_initvar(J, F->vartab[i], -1); js_pop(J, 1); } jsR_run(J, F); v = *stackidx(J, -1); TOP = --BOT; /* clear stack */ js_pushvalue(J, v); jsR_restorescope(J); } static void jsR_callscript(js_State *J, int n, js_Function *F, js_Environment *scope) { js_Value v; int i; if (scope) jsR_savescope(J, scope); /* scripts take no arguments */ js_pop(J, n); for (i = 0; i < F->varlen; ++i) { js_pushundefined(J); js_initvar(J, F->vartab[i], -1); js_pop(J, 1); } jsR_run(J, F); v = *stackidx(J, -1); TOP = --BOT; /* clear stack */ js_pushvalue(J, v); if (scope) jsR_restorescope(J); } static void jsR_callcfunction(js_State *J, int n, int min, js_CFunction F) { int i; js_Value v; for (i = n; i < min; ++i) js_pushundefined(J); F(J); v = *stackidx(J, -1); TOP = --BOT; /* clear stack */ js_pushvalue(J, v); } static void jsR_pushtrace(js_State *J, const char *name, const char *file, int line) { if (J->tracetop + 1 == JS_ENVLIMIT) js_error(J, "call stack overflow"); ++J->tracetop; J->trace[J->tracetop].name = name; J->trace[J->tracetop].file = file; J->trace[J->tracetop].line = line; } void js_call(js_State *J, int n) { js_Object *obj; int savebot; if (!js_iscallable(J, -n-2)) js_typeerror(J, "%s is not callable", js_typeof(J, -n-2)); obj = js_toobject(J, -n-2); savebot = BOT; BOT = TOP - n - 1; if (obj->type == JS_CFUNCTION) { jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line); if (obj->u.f.function->lightweight) jsR_calllwfunction(J, n, obj->u.f.function, obj->u.f.scope); else jsR_callfunction(J, n, obj->u.f.function, obj->u.f.scope); --J->tracetop; } else if (obj->type == JS_CSCRIPT) { jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line); jsR_callscript(J, n, obj->u.f.function, obj->u.f.scope); --J->tracetop; } else if (obj->type == JS_CEVAL) { jsR_pushtrace(J, obj->u.f.function->name, obj->u.f.function->filename, obj->u.f.function->line); jsR_calleval(J, n, obj->u.f.function, obj->u.f.scope); --J->tracetop; } else if (obj->type == JS_CCFUNCTION) { jsR_pushtrace(J, obj->u.c.name, "native", 0); jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.function); --J->tracetop; } BOT = savebot; } void js_construct(js_State *J, int n) { js_Object *obj; js_Object *prototype; js_Object *newobj; if (!js_iscallable(J, -n-1)) js_typeerror(J, "%s is not callable", js_typeof(J, -n-1)); obj = js_toobject(J, -n-1); /* built-in constructors create their own objects, give them a 'null' this */ if (obj->type == JS_CCFUNCTION && obj->u.c.constructor) { int savebot = BOT; js_pushnull(J); if (n > 0) js_rot(J, n + 1); BOT = TOP - n - 1; jsR_pushtrace(J, obj->u.c.name, "native", 0); jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.constructor); --J->tracetop; BOT = savebot; return; } /* extract the function object's prototype property */ js_getproperty(J, -n - 1, "prototype"); if (js_isobject(J, -1)) prototype = js_toobject(J, -1); else prototype = J->Object_prototype; js_pop(J, 1); /* create a new object with above prototype, and shift it into the 'this' slot */ newobj = jsV_newobject(J, JS_COBJECT, prototype); js_pushobject(J, newobj); if (n > 0) js_rot(J, n + 1); /* call the function */ js_call(J, n); /* if result is not an object, return the original object we created */ if (!js_isobject(J, -1)) { js_pop(J, 1); js_pushobject(J, newobj); } } void js_eval(js_State *J) { if (!js_isstring(J, -1)) return; js_loadeval(J, "(eval)", js_tostring(J, -1)); js_rot2pop1(J); js_copy(J, 0); /* copy 'this' */ js_call(J, 0); } int js_pconstruct(js_State *J, int n) { int savetop = TOP - n - 2; if (js_try(J)) { /* clean up the stack to only hold the error object */ STACK[savetop] = STACK[TOP-1]; TOP = savetop + 1; return 1; } js_construct(J, n); js_endtry(J); return 0; } int js_pcall(js_State *J, int n) { int savetop = TOP - n - 2; if (js_try(J)) { /* clean up the stack to only hold the error object */ STACK[savetop] = STACK[TOP-1]; TOP = savetop + 1; return 1; } js_call(J, n); js_endtry(J); return 0; } /* Exceptions */ void *js_savetrypc(js_State *J, js_Instruction *pc) { if (J->trytop == JS_TRYLIMIT) js_error(J, "try: exception stack overflow"); J->trybuf[J->trytop].E = J->E; J->trybuf[J->trytop].envtop = J->envtop; J->trybuf[J->trytop].tracetop = J->tracetop; J->trybuf[J->trytop].top = J->top; J->trybuf[J->trytop].bot = J->bot; J->trybuf[J->trytop].strict = J->strict; J->trybuf[J->trytop].pc = pc; return J->trybuf[J->trytop++].buf; } void *js_savetry(js_State *J) { if (J->trytop == JS_TRYLIMIT) js_error(J, "try: exception stack overflow"); J->trybuf[J->trytop].E = J->E; J->trybuf[J->trytop].envtop = J->envtop; J->trybuf[J->trytop].tracetop = J->tracetop; J->trybuf[J->trytop].top = J->top; J->trybuf[J->trytop].bot = J->bot; J->trybuf[J->trytop].strict = J->strict; J->trybuf[J->trytop].pc = NULL; return J->trybuf[J->trytop++].buf; } void js_endtry(js_State *J) { if (J->trytop == 0) js_error(J, "endtry: exception stack underflow"); --J->trytop; } void js_throw(js_State *J) { if (J->trytop > 0) { js_Value v = *stackidx(J, -1); --J->trytop; J->E = J->trybuf[J->trytop].E; J->envtop = J->trybuf[J->trytop].envtop; J->tracetop = J->trybuf[J->trytop].tracetop; J->top = J->trybuf[J->trytop].top; J->bot = J->trybuf[J->trytop].bot; J->strict = J->trybuf[J->trytop].strict; js_pushvalue(J, v); longjmp(J->trybuf[J->trytop].buf, 1); } if (J->panic) J->panic(J); abort(); } /* Main interpreter loop */ static void jsR_dumpstack(js_State *J) { int i; printf("stack {\n"); for (i = 0; i < TOP; ++i) { putchar(i == BOT ? '>' : ' '); printf("%4d: ", i); js_dumpvalue(J, STACK[i]); putchar('\n'); } printf("}\n"); } static void jsR_dumpenvironment(js_State *J, js_Environment *E, int d) { printf("scope %d ", d); js_dumpobject(J, E->variables); if (E->outer) jsR_dumpenvironment(J, E->outer, d+1); } void js_stacktrace(js_State *J) { int n; printf("stack trace:\n"); for (n = J->tracetop; n >= 0; --n) { const char *name = J->trace[n].name; const char *file = J->trace[n].file; int line = J->trace[n].line; if (line > 0) { if (name[0]) printf("\tat %s (%s:%d)\n", name, file, line); else printf("\tat %s:%d\n", file, line); } else printf("\tat %s (%s)\n", name, file); } } void js_trap(js_State *J, int pc) { if (pc > 0) { js_Function *F = STACK[BOT-1].u.object->u.f.function; printf("trap at %d in function ", pc); jsC_dumpfunction(J, F); } jsR_dumpstack(J); jsR_dumpenvironment(J, J->E, 0); js_stacktrace(J); } static void jsR_run(js_State *J, js_Function *F) { js_Function **FT = F->funtab; double *NT = F->numtab; const char **ST = F->strtab; const char **VT = F->vartab-1; int lightweight = F->lightweight; js_Instruction *pcstart = F->code; js_Instruction *pc = F->code; enum js_OpCode opcode; int offset; int savestrict; const char *str; js_Object *obj; double x, y; unsigned int ux, uy; int ix, iy, okay; int b; savestrict = J->strict; J->strict = F->strict; while (1) { if (J->gccounter > J->gcthresh) js_gc(J, 0); J->trace[J->tracetop].line = *pc++; opcode = *pc++; switch (opcode) { case OP_POP: js_pop(J, 1); break; case OP_DUP: js_dup(J); break; case OP_DUP2: js_dup2(J); break; case OP_ROT2: js_rot2(J); break; case OP_ROT3: js_rot3(J); break; case OP_ROT4: js_rot4(J); break; case OP_INTEGER: js_pushnumber(J, *pc++ - 32768); break; case OP_NUMBER: js_pushnumber(J, NT[*pc++]); break; case OP_STRING: js_pushliteral(J, ST[*pc++]); break; case OP_CLOSURE: js_newfunction(J, FT[*pc++], J->E); break; case OP_NEWOBJECT: js_newobject(J); break; case OP_NEWARRAY: js_newarray(J); break; case OP_NEWREGEXP: js_newregexp(J, ST[pc[0]], pc[1]); pc += 2; break; case OP_UNDEF: js_pushundefined(J); break; case OP_NULL: js_pushnull(J); break; case OP_TRUE: js_pushboolean(J, 1); break; case OP_FALSE: js_pushboolean(J, 0); break; case OP_THIS: if (J->strict) { js_copy(J, 0); } else { if (js_iscoercible(J, 0)) js_copy(J, 0); else js_pushglobal(J); } break; case OP_CURRENT: js_currentfunction(J); break; case OP_GETLOCAL: if (lightweight) { CHECKSTACK(1); STACK[TOP++] = STACK[BOT + *pc++]; } else { str = VT[*pc++]; if (!js_hasvar(J, str)) js_referenceerror(J, "'%s' is not defined", str); } break; case OP_SETLOCAL: if (lightweight) { STACK[BOT + *pc++] = STACK[TOP-1]; } else { js_setvar(J, VT[*pc++]); } break; case OP_DELLOCAL: if (lightweight) { ++pc; js_pushboolean(J, 0); } else { b = js_delvar(J, VT[*pc++]); js_pushboolean(J, b); } break; case OP_GETVAR: str = ST[*pc++]; if (!js_hasvar(J, str)) js_referenceerror(J, "'%s' is not defined", str); break; case OP_HASVAR: if (!js_hasvar(J, ST[*pc++])) js_pushundefined(J); break; case OP_SETVAR: js_setvar(J, ST[*pc++]); break; case OP_DELVAR: b = js_delvar(J, ST[*pc++]); js_pushboolean(J, b); break; case OP_IN: str = js_tostring(J, -2); if (!js_isobject(J, -1)) js_typeerror(J, "operand to 'in' is not an object"); b = js_hasproperty(J, -1, str); js_pop(J, 2 + b); js_pushboolean(J, b); break; case OP_INITPROP: obj = js_toobject(J, -3); str = js_tostring(J, -2); jsR_setproperty(J, obj, str); js_pop(J, 2); break; case OP_INITGETTER: obj = js_toobject(J, -3); str = js_tostring(J, -2); jsR_defproperty(J, obj, str, 0, NULL, jsR_tofunction(J, -1), NULL); js_pop(J, 2); break; case OP_INITSETTER: obj = js_toobject(J, -3); str = js_tostring(J, -2); jsR_defproperty(J, obj, str, 0, NULL, NULL, jsR_tofunction(J, -1)); js_pop(J, 2); break; case OP_GETPROP: str = js_tostring(J, -1); obj = js_toobject(J, -2); jsR_getproperty(J, obj, str); js_rot3pop2(J); break; case OP_GETPROP_S: str = ST[*pc++]; obj = js_toobject(J, -1); jsR_getproperty(J, obj, str); js_rot2pop1(J); break; case OP_SETPROP: str = js_tostring(J, -2); obj = js_toobject(J, -3); jsR_setproperty(J, obj, str); js_rot3pop2(J); break; case OP_SETPROP_S: str = ST[*pc++]; obj = js_toobject(J, -2); jsR_setproperty(J, obj, str); js_rot2pop1(J); break; case OP_DELPROP: str = js_tostring(J, -1); obj = js_toobject(J, -2); b = jsR_delproperty(J, obj, str); js_pop(J, 2); js_pushboolean(J, b); break; case OP_DELPROP_S: str = ST[*pc++]; obj = js_toobject(J, -1); b = jsR_delproperty(J, obj, str); js_pop(J, 1); js_pushboolean(J, b); break; case OP_ITERATOR: if (js_iscoercible(J, -1)) { obj = jsV_newiterator(J, js_toobject(J, -1), 0); js_pop(J, 1); js_pushobject(J, obj); } break; case OP_NEXTITER: if (js_isobject(J, -1)) { obj = js_toobject(J, -1); str = jsV_nextiterator(J, obj); if (str) { js_pushliteral(J, str); js_pushboolean(J, 1); } else { js_pop(J, 1); js_pushboolean(J, 0); } } else { js_pop(J, 1); js_pushboolean(J, 0); } break; /* Function calls */ case OP_EVAL: js_eval(J); break; case OP_CALL: js_call(J, *pc++); break; case OP_NEW: js_construct(J, *pc++); break; /* Unary operators */ case OP_TYPEOF: str = js_typeof(J, -1); js_pop(J, 1); js_pushliteral(J, str); break; case OP_POS: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, x); break; case OP_NEG: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, -x); break; case OP_BITNOT: ix = js_toint32(J, -1); js_pop(J, 1); js_pushnumber(J, ~ix); break; case OP_LOGNOT: b = js_toboolean(J, -1); js_pop(J, 1); js_pushboolean(J, !b); break; case OP_INC: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, x + 1); break; case OP_DEC: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, x - 1); break; case OP_POSTINC: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, x + 1); js_pushnumber(J, x); break; case OP_POSTDEC: x = js_tonumber(J, -1); js_pop(J, 1); js_pushnumber(J, x - 1); js_pushnumber(J, x); break; /* Multiplicative operators */ case OP_MUL: x = js_tonumber(J, -2); y = js_tonumber(J, -1); js_pop(J, 2); js_pushnumber(J, x * y); break; case OP_DIV: x = js_tonumber(J, -2); y = js_tonumber(J, -1); js_pop(J, 2); js_pushnumber(J, x / y); break; case OP_MOD: x = js_tonumber(J, -2); y = js_tonumber(J, -1); js_pop(J, 2); js_pushnumber(J, fmod(x, y)); break; /* Additive operators */ case OP_ADD: js_concat(J); break; case OP_SUB: x = js_tonumber(J, -2); y = js_tonumber(J, -1); js_pop(J, 2); js_pushnumber(J, x - y); break; /* Shift operators */ case OP_SHL: ix = js_toint32(J, -2); uy = js_touint32(J, -1); js_pop(J, 2); js_pushnumber(J, ix << (uy & 0x1F)); break; case OP_SHR: ix = js_toint32(J, -2); uy = js_touint32(J, -1); js_pop(J, 2); js_pushnumber(J, ix >> (uy & 0x1F)); break; case OP_USHR: ux = js_touint32(J, -2); uy = js_touint32(J, -1); js_pop(J, 2); js_pushnumber(J, ux >> (uy & 0x1F)); break; /* Relational operators */ case OP_LT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b < 0); break; case OP_GT: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b > 0); break; case OP_LE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b <= 0); break; case OP_GE: b = js_compare(J, &okay); js_pop(J, 2); js_pushboolean(J, okay && b >= 0); break; case OP_INSTANCEOF: b = js_instanceof(J); js_pop(J, 2); js_pushboolean(J, b); break; /* Equality */ case OP_EQ: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, b); break; case OP_NE: b = js_equal(J); js_pop(J, 2); js_pushboolean(J, !b); break; case OP_STRICTEQ: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, b); break; case OP_STRICTNE: b = js_strictequal(J); js_pop(J, 2); js_pushboolean(J, !b); break; case OP_JCASE: offset = *pc++; b = js_strictequal(J); if (b) { js_pop(J, 2); pc = pcstart + offset; } else { js_pop(J, 1); } break; /* Binary bitwise operators */ case OP_BITAND: ix = js_toint32(J, -2); iy = js_toint32(J, -1); js_pop(J, 2); js_pushnumber(J, ix & iy); break; case OP_BITXOR: ix = js_toint32(J, -2); iy = js_toint32(J, -1); js_pop(J, 2); js_pushnumber(J, ix ^ iy); break; case OP_BITOR: ix = js_toint32(J, -2); iy = js_toint32(J, -1); js_pop(J, 2); js_pushnumber(J, ix | iy); break; /* Try and Catch */ case OP_THROW: js_throw(J); case OP_TRY: offset = *pc++; if (js_trypc(J, pc)) { pc = J->trybuf[J->trytop].pc; } else { pc = pcstart + offset; } break; case OP_ENDTRY: js_endtry(J); break; case OP_CATCH: str = ST[*pc++]; obj = jsV_newobject(J, JS_COBJECT, NULL); js_pushobject(J, obj); js_rot2(J); js_setproperty(J, -2, str); J->E = jsR_newenvironment(J, obj, J->E); js_pop(J, 1); break; case OP_ENDCATCH: J->E = J->E->outer; break; /* With */ case OP_WITH: obj = js_toobject(J, -1); J->E = jsR_newenvironment(J, obj, J->E); js_pop(J, 1); break; case OP_ENDWITH: J->E = J->E->outer; break; /* Branching */ case OP_DEBUGGER: js_trap(J, (int)(pc - pcstart) - 1); break; case OP_JUMP: pc = pcstart + *pc; break; case OP_JTRUE: offset = *pc++; b = js_toboolean(J, -1); js_pop(J, 1); if (b) pc = pcstart + offset; break; case OP_JFALSE: offset = *pc++; b = js_toboolean(J, -1); js_pop(J, 1); if (!b) pc = pcstart + offset; break; case OP_RETURN: J->strict = savestrict; return; } } }