shithub: libmujs

Download patch

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",