ref: 031513b25b73ce5e5fc7d6bafd512a822d8ded76
parent: 5a48671015d5cf491327daf876e78aed05c4e20d
author: Tor Andersson <tor@ccxvii.net>
date: Tue Dec 2 20:18:21 EST 2014
Add stack traces to error objects. Revert 'add context and flag argument to js_newstate' commit. The context argument just adds clutter. The flag which was intended for JS_DEBUG and/or JS_STRICT shouldn't be necessary. js_newcfunction and js_newcconstructor need an extra argument, the name of the function to use in stack traces.
--- a/jsarray.c
+++ b/jsarray.c
@@ -709,7 +709,7 @@
jsB_propf(J, "reduce", Ap_reduce, 1);
jsB_propf(J, "reduceRight", Ap_reduceRight, 1);
}
- js_newcconstructor(J, jsB_new_Array, jsB_new_Array, 1);
+ js_newcconstructor(J, jsB_new_Array, jsB_new_Array, "Array", 1);
{
/* ES5 */
jsB_propf(J, "isArray", A_isArray, 1);
--- a/jsboolean.c
+++ b/jsboolean.c
@@ -35,6 +35,6 @@
jsB_propf(J, "toString", Bp_toString, 0);
jsB_propf(J, "valueOf", Bp_valueOf, 0);
}
- js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean, 1);
+ js_newcconstructor(J, jsB_Boolean, jsB_new_Boolean, "Boolean", 1);
js_defglobal(J, "Boolean", JS_DONTENUM);
}
--- a/jsbuiltin.c
+++ b/jsbuiltin.c
@@ -6,13 +6,13 @@
static void jsB_globalf(js_State *J, const char *name, js_CFunction cfun, int n)
{
- js_newcfunction(J, cfun, n);
+ js_newcfunction(J, cfun, name, n);
js_defglobal(J, name, JS_DONTENUM);
}
void jsB_propf(js_State *J, const char *name, js_CFunction cfun, int n)
{
- js_newcfunction(J, cfun, n);
+ js_newcfunction(J, cfun, name, n);
js_defproperty(J, -2, name, JS_DONTENUM);
}
--- a/jscompile.c
+++ b/jscompile.c
@@ -67,6 +67,15 @@
emitraw(J, F, value);
}
+static void emitline(JF, js_Ast *node)
+{
+ if (F->lastline != node->line) {
+ F->lastline = node->line;
+ emit(J, F, OP_LINE);
+ emitraw(J, F, node->line);
+ }
+}
+
static int addfunction(JF, js_Function *value)
{
if (F->funlen >= F->funcap) {
@@ -892,6 +901,8 @@
{
js_Ast *target;
int loop, cont, then, end;
+
+ emitline(J, F, stm);
switch (stm->type) {
case AST_FUNDEC:
--- a/jscompile.h
+++ b/jscompile.h
@@ -115,6 +115,8 @@
OP_JTRUE,
OP_JFALSE,
OP_RETURN,
+
+ OP_LINE, /* -K- */
};
struct js_Function
@@ -141,7 +143,7 @@
unsigned int varcap, varlen;
const char *filename;
- int line;
+ int line, lastline;
js_Function *gcnext;
int gcmark;
--- a/jsdate.c
+++ b/jsdate.c
@@ -792,7 +792,7 @@
jsB_propf(J, "toISOString", Dp_toISOString, 0);
jsB_propf(J, "toJSON", Dp_toJSON, 1);
}
- js_newcconstructor(J, jsB_Date, jsB_new_Date, 1);
+ js_newcconstructor(J, jsB_Date, jsB_new_Date, "Date", 1);
{
jsB_propf(J, "parse", D_parse, 1);
jsB_propf(J, "UTC", D_UTC, 7);
--- a/jsdump.c
+++ b/jsdump.c
@@ -365,7 +365,7 @@
case EXP_MEMBER:
pexpi(d, p, exp->a);
pc('.');
- pexpi(d, p, exp->b);
+ pexpi(d, 0, exp->b);
break;
case EXP_CALL:
@@ -759,6 +759,7 @@
ps(F->strtab[*p++]);
break;
+ case OP_LINE:
case OP_CLOSURE:
case OP_INITLOCAL:
case OP_GETLOCAL:
--- a/jserror.c
+++ b/jserror.c
@@ -5,8 +5,27 @@
#define QQ(X) #X
#define Q(X) QQ(X)
+static void jsB_stacktrace(js_State *J, int skip)
+{
+ int n;
+ char buf[256];
+ for (n = J->tracetop - skip; 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)
+ snprintf(buf, sizeof buf, "\n\t%s:%d: in function '%s'", file, line, name);
+ else
+ snprintf(buf, sizeof buf, "\n\t%s: in function '%s'", file, name);
+ js_pushstring(J, buf);
+ if (n < J->tracetop - skip)
+ js_concat(J);
+ }
+}
+
static void Ep_toString(js_State *J)
{
+ char buf[256];
const char *name = "Error";
const char *message = "";
@@ -13,36 +32,28 @@
if (!js_isobject(J, -1))
js_typeerror(J, "not an object");
- js_getproperty(J, 0, "name");
- if (js_isdefined(J, -1))
+ if (js_hasproperty(J, 0, "name"))
name = js_tostring(J, -1);
- js_pop(J, 1);
-
- js_getproperty(J, 0, "message");
- if (js_isdefined(J, -1))
+ if (js_hasproperty(J, 0, "message"))
message = js_tostring(J, -1);
- js_pop(J, 1);
- if (!strcmp(name, ""))
- js_pushstring(J, message);
- else if (!strcmp(message, ""))
- js_pushstring(J, name);
- else {
- js_pushstring(J, name);
- js_pushstring(J, ": ");
+ snprintf(buf, sizeof buf, "%s: %s", name, message);
+ js_pushstring(J, buf);
+
+ if (js_hasproperty(J, 0, "stackTrace"))
js_concat(J);
- js_pushstring(J, message);
- js_concat(J);
- }
}
static int jsB_ErrorX(js_State *J, js_Object *prototype)
{
+ unsigned int top = js_gettop(J);
js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype));
- if (js_isdefined(J, 1)) {
+ if (top > 1) {
js_pushstring(J, js_tostring(J, 1));
js_setproperty(J, -2, "message");
}
+ jsB_stacktrace(J, 1);
+ js_setproperty(J, -2, "stackTrace");
return 1;
}
@@ -51,6 +62,8 @@
js_pushobject(J, jsV_newobject(J, JS_CERROR, prototype));
js_pushstring(J, message);
js_setproperty(J, -2, "message");
+ jsB_stacktrace(J, 0);
+ js_setproperty(J, -2, "stackTrace");
}
#define DERROR(name, Name) \
@@ -88,13 +101,13 @@
jsB_props(J, "message", "an error has occurred");
jsB_propf(J, "toString", Ep_toString, 0);
}
- js_newcconstructor(J, jsB_Error, jsB_Error, 1);
+ js_newcconstructor(J, jsB_Error, jsB_Error, "Error", 1);
js_defglobal(J, "Error", JS_DONTENUM);
#define IERROR(NAME) \
js_pushobject(J, J->NAME##_prototype); \
jsB_props(J, "name", Q(NAME)); \
- js_newcconstructor(J, jsB_##NAME, jsB_##NAME, 1); \
+ js_newcconstructor(J, jsB_##NAME, jsB_##NAME, Q(NAME), 1); \
js_defglobal(J, Q(NAME), JS_DONTENUM);
IERROR(EvalError);
--- a/jsfunction.c
+++ b/jsfunction.c
@@ -31,7 +31,7 @@
js_throw(J);
}
- parse = jsP_parsefunction(J, "Function", sb ? sb->s : NULL, body);
+ parse = jsP_parsefunction(J, "[string]", sb ? sb->s : NULL, body);
fun = jsC_compilefunction(J, parse);
js_endtry(J);
@@ -171,7 +171,7 @@
else
n = 0;
- js_newcconstructor(J, callbound, constructbound, n);
+ js_newcconstructor(J, callbound, constructbound, "[bind]", n);
/* Reuse target function's prototype for HasInstance check. */
js_getproperty(J, 0, "prototype");
@@ -206,6 +206,6 @@
jsB_propf(J, "call", Fp_call, 1);
jsB_propf(J, "bind", Fp_bind, 1);
}
- js_newcconstructor(J, jsB_Function, jsB_Function, 1);
+ js_newcconstructor(J, jsB_Function, jsB_Function, "Function", 1);
js_defglobal(J, "Function", JS_DONTENUM);
}
--- a/jsi.h
+++ b/jsi.h
@@ -46,6 +46,7 @@
typedef struct js_Environment js_Environment;
typedef struct js_StringNode js_StringNode;
typedef struct js_Jumpbuf js_Jumpbuf;
+typedef struct js_StackTrace js_StackTrace;
/* Limits */
@@ -95,6 +96,13 @@
void js_trap(js_State *J, int pc); /* dump stack and environment to stdout */
+struct js_StackTrace
+{
+ const char *name;
+ const char *file;
+ int line;
+};
+
/* Exception handling */
struct js_Jumpbuf
@@ -102,6 +110,7 @@
jmp_buf buf;
js_Environment *E;
int envtop;
+ int tracetop;
int top, bot;
js_Instruction *pc;
};
@@ -109,13 +118,13 @@
void js_savetry(js_State *J, js_Instruction *pc);
#define js_trypc(J, PC) \
- (js_savetry(J, PC), setjmp(J->trybuf[J->trylen++].buf))
+ (js_savetry(J, PC), setjmp(J->trybuf[J->trytop++].buf))
#define js_try(J) \
- (js_savetry(J, NULL), setjmp(J->trybuf[J->trylen++].buf))
+ (js_savetry(J, NULL), setjmp(J->trybuf[J->trytop++].buf))
#define js_endtry(J) \
- (--J->trylen)
+ (--J->trytop)
/* State struct */
@@ -141,6 +150,7 @@
int newline;
/* parser state */
+ int astline;
int lookahead;
const char *text;
double number;
@@ -185,12 +195,17 @@
js_Object *gcobj;
js_String *gcstr;
+
/* environments on the call stack but currently not in scope */
int envtop;
js_Environment *envstack[JS_ENVLIMIT];
+ /* debug info stack trace */
+ int tracetop;
+ js_StackTrace trace[JS_ENVLIMIT];
+
/* exception stack */
- int trylen;
+ int trytop;
js_Jumpbuf trybuf[JS_TRYLIMIT];
};
--- a/jsnumber.c
+++ b/jsnumber.c
@@ -89,7 +89,7 @@
jsB_propf(J, "toExponential", Np_toExponential, 1);
jsB_propf(J, "toPrecision", Np_toPrecision, 1);
}
- js_newcconstructor(J, jsB_Number, jsB_new_Number, 1);
+ js_newcconstructor(J, jsB_Number, jsB_new_Number, "Number", 1);
{
jsB_propn(J, "MAX_VALUE", 1.7976931348623157e+308);
jsB_propn(J, "MIN_VALUE", 5e-324);
--- a/jsobject.c
+++ b/jsobject.c
@@ -425,7 +425,7 @@
jsB_propf(J, "isPrototypeOf", Op_isPrototypeOf, 1);
jsB_propf(J, "propertyIsEnumerable", Op_propertyIsEnumerable, 1);
}
- js_newcconstructor(J, jsB_Object, jsB_new_Object, 1);
+ js_newcconstructor(J, jsB_Object, jsB_new_Object, "Object", 1);
{
/* ES5 */
jsB_propf(J, "getPrototypeOf", O_getPrototypeOf, 1);
--- a/jsparse.c
+++ b/jsparse.c
@@ -57,7 +57,7 @@
js_Ast *node = js_malloc(J, sizeof *node);
node->type = type;
- node->line = J->lexline;
+ node->line = J->astline;
node->a = a;
node->b = b;
node->c = c;
@@ -130,6 +130,7 @@
static void next(js_State *J)
{
+ J->astline = J->lexline;
J->lookahead = jsY_lex(J);
}
--- a/jsregexp.c
+++ b/jsregexp.c
@@ -189,6 +189,6 @@
jsB_propf(J, "test", Rp_test, 0);
jsB_propf(J, "exec", Rp_exec, 0);
}
- js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, 1);
+ js_newcconstructor(J, jsB_RegExp, jsB_new_RegExp, "RegExp", 1);
js_defglobal(J, "RegExp", JS_DONTENUM);
}
--- a/jsrun.c
+++ b/jsrun.c
@@ -934,6 +934,15 @@
js_pushvalue(J, v);
}
+static void jsR_pushtrace(js_State *J, const char *name, const char *file, int line)
+{
+ if (++J->tracetop == JS_ENVLIMIT)
+ js_error(J, "call stack overflow");
+ 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;
@@ -948,14 +957,21 @@
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);
- } else if (obj->type == JS_CSCRIPT)
+ --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);
- else if (obj->type == JS_CCFUNCTION)
+ --J->tracetop;
+ } else if (obj->type == JS_CCFUNCTION) {
+ jsR_pushtrace(J, obj->u.c.name, "[C]", 0);
jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.function);
+ --J->tracetop;
+ }
BOT = savebot;
}
@@ -978,7 +994,11 @@
if (n > 0)
js_rot(J, n + 1);
BOT = TOP - n - 1;
+
+ jsR_pushtrace(J, obj->u.c.name, "[C]", 0);
jsR_callcfunction(J, n, obj->u.c.length, obj->u.c.constructor);
+ --J->tracetop;
+
BOT = savebot;
return;
}
@@ -1029,26 +1049,28 @@
void js_savetry(js_State *J, js_Instruction *pc)
{
- if (J->trylen == JS_TRYLIMIT)
+ if (J->trytop == JS_TRYLIMIT)
js_error(J, "try: exception stack overflow");
- J->trybuf[J->trylen].E = J->E;
- J->trybuf[J->trylen].envtop = J->envtop;
- J->trybuf[J->trylen].top = J->top;
- J->trybuf[J->trylen].bot = J->bot;
- J->trybuf[J->trylen].pc = pc;
+ 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].pc = pc;
}
void js_throw(js_State *J)
{
- if (J->trylen > 0) {
+ if (J->trytop > 0) {
js_Value v = *stackidx(J, -1);
- --J->trylen;
- J->E = J->trybuf[J->trylen].E;
- J->envtop = J->trybuf[J->trylen].envtop;
- J->top = J->trybuf[J->trylen].top;
- J->bot = J->trybuf[J->trylen].bot;
+ --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;
js_pushvalue(J, v);
- longjmp(J->trybuf[J->trylen].buf, 1);
+ longjmp(J->trybuf[J->trytop].buf, 1);
}
if (J->panic)
J->panic(J);
@@ -1078,13 +1100,31 @@
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)
+ printf("\t%s:%d: in function '%s'\n", file, line, name);
+ else
+ printf("\t%s: in function '%s'\n", file, name);
+ }
+}
+
void js_trap(js_State *J, int pc)
{
- js_Function *F = STACK[BOT-1].u.object->u.f.function;
- printf("trap at %d in function ", pc);
- jsC_dumpfunction(J, F);
+ 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)
@@ -1490,7 +1530,7 @@
case OP_TRY:
offset = *pc++;
if (js_trypc(J, pc)) {
- pc = J->trybuf[J->trylen].pc;
+ pc = J->trybuf[J->trytop].pc;
} else {
pc = pcstart + offset;
}
@@ -1554,6 +1594,10 @@
case OP_RETURN:
return;
+
+ case OP_LINE:
+ J->trace[J->tracetop].line = *pc++;
+ break;
}
}
}
--- a/jsstate.c
+++ b/jsstate.c
@@ -18,7 +18,7 @@
static void js_defaultpanic(js_State *J)
{
- fprintf(stderr, "mujs: uncaught exception: %s\n", js_tostring(J, -1));
+ fprintf(stderr, "uncaught exception: %s\n", js_tostring(J, -1));
/* return to javascript to abort */
}
@@ -117,11 +117,11 @@
int js_dostring(js_State *J, const char *source, int report)
{
if (js_try(J)) {
- fprintf(stderr, "mujs: %s\n", js_tostring(J, -1));
+ fprintf(stderr, "%s\n", js_tostring(J, -1));
js_pop(J, 1);
return 1;
}
- js_loadstring(J, "(string)", source);
+ js_loadstring(J, "[string]", source);
js_pushglobal(J);
js_call(J, 0);
if (report)
@@ -135,7 +135,7 @@
int js_dofile(js_State *J, const char *filename)
{
if (js_try(J)) {
- fprintf(stderr, "mujs: %s\n", js_tostring(J, -1));
+ fprintf(stderr, "%s\n", js_tostring(J, -1));
js_pop(J, 1);
return 1;
}
@@ -164,7 +164,7 @@
return J->uctx;
}
-js_State *js_newstate(js_Alloc alloc, void *actx, void *uctx, int flags)
+js_State *js_newstate(js_Alloc alloc, void *actx)
{
js_State *J;
@@ -175,9 +175,12 @@
if (!J)
return NULL;
memset(J, 0, sizeof(*J));
- J->uctx = uctx;
J->actx = actx;
J->alloc = alloc;
+
+ J->trace[0].name = "?";
+ J->trace[0].file = "[C]";
+ J->trace[0].line = 0;
J->panic = js_defaultpanic;
--- a/jsstring.c
+++ b/jsstring.c
@@ -679,7 +679,7 @@
/* ES5 */
jsB_propf(J, "trim", Sp_trim, 0);
}
- js_newcconstructor(J, jsB_String, jsB_new_String, 1);
+ js_newcconstructor(J, jsB_String, jsB_new_String, "String", 1);
{
jsB_propf(J, "fromCharCode", S_fromCharCode, 1);
}
--- a/jsvalue.c
+++ b/jsvalue.c
@@ -390,9 +390,10 @@
js_pushobject(J, obj);
}
-void js_newcfunction(js_State *J, js_CFunction cfun, unsigned int length)
+void js_newcfunction(js_State *J, js_CFunction cfun, const char *name, unsigned int length)
{
js_Object *obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype);
+ obj->u.c.name = name;
obj->u.c.function = cfun;
obj->u.c.constructor = NULL;
obj->u.c.length = length;
@@ -410,9 +411,10 @@
}
/* prototype -- constructor */
-void js_newcconstructor(js_State *J, js_CFunction cfun, js_CFunction ccon, unsigned int length)
+void js_newcconstructor(js_State *J, js_CFunction cfun, js_CFunction ccon, const char *name, unsigned int length)
{
js_Object *obj = jsV_newobject(J, JS_CCFUNCTION, J->Function_prototype);
+ obj->u.c.name = name;
obj->u.c.function = cfun;
obj->u.c.constructor = ccon;
js_pushobject(J, obj); /* proto obj */
--- a/jsvalue.h
+++ b/jsvalue.h
@@ -92,6 +92,7 @@
js_Environment *scope;
} f;
struct {
+ const char *name;
js_CFunction function;
js_CFunction constructor;
unsigned int length;
--- a/main.c
+++ b/main.c
@@ -117,27 +117,27 @@
js_State *J;
int i;
- J = js_newstate(NULL, NULL, NULL, 0);
+ J = js_newstate(NULL, NULL);
- js_newcfunction(J, jsB_gc, 0);
+ js_newcfunction(J, jsB_gc, "gc", 0);
js_setglobal(J, "gc");
- js_newcfunction(J, jsB_load, 1);
+ js_newcfunction(J, jsB_load, "load", 1);
js_setglobal(J, "load");
- js_newcfunction(J, jsB_print, 1);
+ js_newcfunction(J, jsB_print, "print", 1);
js_setglobal(J, "print");
- js_newcfunction(J, jsB_write, 0);
+ js_newcfunction(J, jsB_write, "write", 0);
js_setglobal(J, "write");
- js_newcfunction(J, jsB_read, 1);
+ js_newcfunction(J, jsB_read, "read", 1);
js_setglobal(J, "read");
- js_newcfunction(J, jsB_readline, 0);
+ js_newcfunction(J, jsB_readline, "readline", 0);
js_setglobal(J, "readline");
- js_newcfunction(J, jsB_quit, 1);
+ js_newcfunction(J, jsB_quit, "quit", 1);
js_setglobal(J, "quit");
js_dostring(J, require_js, 0);
--- a/mujs.h
+++ b/mujs.h
@@ -31,7 +31,7 @@
typedef void (*js_CFunction)(js_State *J);
/* Basic functions */
-js_State *js_newstate(js_Alloc alloc, void *actx, void *uctx, int flags);
+js_State *js_newstate(js_Alloc alloc, void *actx);
void js_setcontext(js_State *J, void *uctx);
void *js_getcontext(js_State *J);
js_Panic js_atpanic(js_State *J, js_Panic panic);
@@ -122,8 +122,8 @@
void js_newboolean(js_State *J, int v);
void js_newnumber(js_State *J, double v);
void js_newstring(js_State *J, const char *v);
-void js_newcfunction(js_State *J, js_CFunction fun, unsigned int length);
-void js_newcconstructor(js_State *J, js_CFunction fun, js_CFunction con, unsigned int length);
+void js_newcfunction(js_State *J, js_CFunction fun, const char *name, unsigned int length);
+void js_newcconstructor(js_State *J, js_CFunction fun, js_CFunction con, const char *name, unsigned int length);
void js_newuserdata(js_State *J, const char *tag, void *data);
void js_newregexp(js_State *J, const char *pattern, int flags);
--- a/opnames.h
+++ b/opnames.h
@@ -89,3 +89,4 @@
"jtrue",
"jfalse",
"return",
+"line",