From 329a03a324b8515338db6b03912b9e167a66de37 Mon Sep 17 00:00:00 2001
From: deuce <>
Date: Tue, 23 Oct 2012 05:06:43 +0000
Subject: [PATCH] Make the debugger actually useful. Added backtrace (bt) and
 up/down commands. Allow specifying breakpoints in file:line format for future
 load()s.

Breakpoints are a small memory leak right now as there's no way to clear them.
---
 src/sbbs3/jsexec.c | 321 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 274 insertions(+), 47 deletions(-)

diff --git a/src/sbbs3/jsexec.c b/src/sbbs3/jsexec.c
index 4583a72bb3..251a4c49d1 100644
--- a/src/sbbs3/jsexec.c
+++ b/src/sbbs3/jsexec.c
@@ -82,13 +82,6 @@ BOOL		daemonize=FALSE;
 #endif
 char		orig_cwd[MAX_PATH+1];
 BOOL		debugger=FALSE;
-enum debug_action {
-	DEBUG_CONTINUE,
-	DEBUG_EXIT
-};
-
-static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure);
-static enum debug_action debug_prompt(JSScript *script);
 
 void banner(FILE* fp)
 {
@@ -776,35 +769,203 @@ static const char* js_ext(const char* fname)
  * This doesn't belong here and is done wrong...
  * mcmlxxix should love it.
  */
-static enum debug_action debug_prompt(JSScript *script)
+#include <link_list.h>
+link_list_t	breakpoints;
+link_list_t	scripts;
+enum debug_action {
+	DEBUG_CONTINUE,
+	DEBUG_EXIT
+};
+static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure);
+static JSTrapStatus throw_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure);
+static enum debug_action debug_prompt(JSContext *cx, JSScript *script);
+
+struct breakpoint {
+	uintN		line;
+	char		name[];
+};
+
+struct cur_script {
+	JSScript	*script;
+	char		*fname;
+	uintN		firstline;
+	uintN		lastline;
+};
+
+static void newscript_handler(JSContext  *cx,
+                    const char *filename,  /* URL of script */
+                    uintN      lineno,     /* first line */
+                    JSScript   *script,
+                    JSFunction *fun,
+                    void       *callerdata)
 {
-	char	line[1024];
+	const char			*fname=NULL;
+	struct cur_script	*cs;
+	list_node_t			*node;
+	struct breakpoint	*bp;
+	jsbytecode			*pc;
+
+	cs=(struct cur_script *)malloc(sizeof(struct cur_script));
+	if(!cs) {
+		fputs("Error allocated script struct", stderr);
+		return;
+	}
+	fname=JS_GetScriptFilename(cx, script);
+	cs->fname=strdup(fname);
+	if(cs->fname)
+		fname=getfname(fname);
+	cs->firstline=lineno;
+	cs->lastline=lineno+JS_GetScriptLineExtent(cx, script);
+	cs->script=script;
+
+	// Loop through breakpoints.
+	for(node=listFirstNode(&breakpoints); node; node=listNextNode(node)) {
+		bp=(struct breakpoint *)node->data;
+		if(strcmp(fname, bp->name)==0 || strcmp(cs->fname, bp->name)==0) {
+			if(bp->line >= cs->firstline && bp->line <= cs->lastline) {
+				pc=JS_LineNumberToPC(cx, script, bp->line);
+				if(pc==NULL) {
+					fprintf(stderr, "NEWSCRIPT: Unable to locate line %u\n", bp->line);
+					break;
+				}
+				if(JS_PCToLineNumber(cx, script, pc)!=bp->line) {
+					continue;
+				}
+				if(!JS_SetTrap(cx, script, pc, trap_handler, JSVAL_VOID)) {
+					fprintf(stderr, "NEWSCRIPT: Unable to set breakpoint at line %u\n",bp->line);
+				}
+			}
+		}
+	}
+	listPushNode(&scripts, cs);
+}
 
+static void killscript_handler(JSContext *cx, JSScript *script, void *callerdata)
+{
+	list_node_t			*node;
+	list_node_t			*pnode;
+	struct cur_script	*cs;
+
+	for(node=listFirstNode(&scripts); node; node=listNextNode(node)) {
+		cs=(struct cur_script *)node->data;
+
+		if(cs->script == script) {
+			pnode=listPrevNode(node);
+			free(cs->fname);
+			listRemoveNode(&scripts, node, TRUE);
+		}
+	}
+}
+
+static void init_debugger(JSRuntime *rt, JSContext *cx)
+{
+	JS_SetDebugMode(cx, JS_TRUE);
+	JS_SetThrowHook(js_runtime, throw_handler, NULL);
+	listInit(&breakpoints,LINK_LIST_MUTEX);
+	listInit(&scripts,LINK_LIST_MUTEX);
+	JS_SetNewScriptHook(rt, newscript_handler, NULL);
+	JS_SetDestroyScriptHook(rt, killscript_handler, NULL);
+}
+
+static enum debug_action debug_prompt(JSContext *cx, JSScript *script)
+{
+	char			line[1024];
+	const char		*fpath;
+	const char		*fname;
+	JSStackFrame	*fp;
+	JSFunction		*fn;
+	jsbytecode		*pc;
+	char			*cp;
+	jsrefcount		rc;
+
+	fp=JS_GetScriptedCaller(cx, NULL);
 	while(1) {
-		if(JS_IsExceptionPending(js_cx))
+		if(JS_IsExceptionPending(cx))
 			fputs("!", stdout);
-		fputs("JS> ", stdout);
+		fpath=JS_GetScriptFilename(cx, script);
+		if(fpath) {
+			fname=getfname(fpath);
+			fputs(fname, stdout);
+			if(fp) {
+				pc=JS_GetFramePC(cx, fp);
+				if(pc)
+					fprintf(stdout, ":%u",JS_PCToLineNumber(cx, script, pc));
+				fn=JS_GetFrameFunction(cx, fp);
+				if(fn) {
+					JSString	*name;
+
+					name=JS_GetFunctionId(fn);
+					if(name) {
+						JSSTRING_TO_STRING(cx, name, cp, NULL);
+						fprintf(stdout, " %s()", cp);
+					}
+				}
+			}
+		}
+		fputs("> ", stdout);
 
+		rc=JS_SUSPENDREQUEST(cx);
 		if(fgets(line, sizeof(line), stdin)==NULL) {
 			fputs("Error readin input\n",stderr);
+			JS_RESUMEREQUEST(cx, rc);
 			continue;
 		}
+		JS_RESUMEREQUEST(cx, rc);
 		if(strncmp(line, "break ", 6)==0) {
-			ulong		linenum=strtoul(line+6, NULL, 10);
-			jsbytecode	*pc;
-			
+			ulong				linenum=0;
+			jsbytecode			*pc;
+			const char			*sname;
+			struct cur_script	*cs;
+			list_node_t			*node;
+			struct breakpoint	*bp;
+			const char			*text;
+			char				*num;
+
+			text=line+6;
+			if(isdigit(*text) && strchr(text,'.')==NULL) {
+				linenum=strtoul(text, NULL, 10);
+				text=fpath;
+			}
+			else {
+				num=strchr(text,':');
+				if(num) {
+					*num=0;
+					num++;
+					if(!(isdigit(*num))) {
+						fputs("Unable to parse breakpoint\n",stderr);
+						continue;
+					}
+					linenum=strtoul(num, NULL, 10);
+				}
+			}
 			if(linenum==ULONG_MAX) {
 				fputs("Unable to parse line number\n",stderr);
 				continue;
 			}
-			pc=JS_LineNumberToPC(js_cx, script, linenum);
-			if(pc==NULL) {
-				fprintf(stderr, "Unable to locate line %lu\n", linenum);
-				break;
+			for(node=listFirstNode(&scripts); node; node=listNextNode(node)) {
+				cs=(struct cur_script *)node->data;
+				sname=getfname(cs->fname);
+				if(strcmp(sname, text)==0 || strcmp(cs->fname, text)==0) {
+					pc=JS_LineNumberToPC(cx, cs->script, linenum);
+					if(JS_PCToLineNumber(cx, cs->script, pc)!=linenum)
+						continue;
+					if(pc==NULL) {
+						fprintf(stderr, "Unable to locate line %lu\n", linenum);
+						continue;
+					}
+					if(!JS_SetTrap(cx, cs->script, pc, trap_handler, JSVAL_VOID)) {
+						fprintf(stderr, "Unable to set breakpoint at line %lu\n",linenum);
+					}
+				}
 			}
-			if(!JS_SetTrap(js_cx, script, pc, trap_handler, JSVAL_VOID)) {
-				fprintf(stderr, "Unable to set breakpoint at line %lu\n",linenum);
+			bp=(struct breakpoint *)malloc(sizeof(uintN)+strlen(text)+1);
+			if(!bp) {
+				fputs("Unable to allocate breakpoint\n",stderr);
+				continue;
 			}
+			bp->line=linenum;
+			strcpy(bp->name, text);
+			listPushNode(&breakpoints, bp);
 			continue;
 		}
 		if(strncmp(line, "r", 1)==0) {
@@ -814,50 +975,118 @@ static enum debug_action debug_prompt(JSScript *script)
 				strncmp(line, "e ", 2)==0
 				) {
 			jsval			ret;
-			JSStackFrame	*fp;
 			jsval			oldexcept;
 			BOOL			has_old=FALSE;
 			int				cmdlen=5;
 
 			if(line[1]==' ')
 				cmdlen=2;
-			if(JS_IsExceptionPending(js_cx)) {
-				if(JS_GetPendingException(js_cx, &oldexcept))
+			if(JS_IsExceptionPending(cx)) {
+				if(JS_GetPendingException(cx, &oldexcept))
 					has_old=TRUE;
-				JS_ClearPendingException(js_cx);
+				JS_ClearPendingException(cx);
 			}
 
-			fp=JS_GetScriptedCaller(js_cx, NULL);
 			if(!fp) {
 				if(has_old)
-					JS_SetPendingException(js_cx, oldexcept);
+					JS_SetPendingException(cx, oldexcept);
 				fputs("Unable to get frame pointer\n", stderr);
 				continue;
 			}
-			if(!JS_EvaluateInStackFrame(js_cx, fp, line+cmdlen, strlen(line)-cmdlen, "eval-d statement", 1, &ret)) {
-				if(JS_IsExceptionPending(js_cx)) {
-					JS_SetErrorReporter(js_cx, js_ErrorReporter);
-					JS_ReportPendingException(js_cx);
-					JS_ClearPendingException(js_cx);
+			if(!JS_EvaluateInStackFrame(cx, fp, line+cmdlen, strlen(line)-cmdlen, "eval-d statement", 1, &ret)) {
+				if(JS_IsExceptionPending(cx)) {
+					JS_SetErrorReporter(cx, js_ErrorReporter);
+					JS_ReportPendingException(cx);
+					JS_ClearPendingException(cx);
 				}
 			}
 			else {
 				// TODO: Check ret...
 			}
 			if(has_old)
-				JS_SetPendingException(js_cx, oldexcept);
+				JS_SetPendingException(cx, oldexcept);
 			continue;
 		}
 		if(strncmp(line, "clear", 5)==0) {
-			JS_ClearPendingException(js_cx);
+			JS_ClearPendingException(cx);
+			continue;
+		}
+		if(strncmp(line, "bt", 2)==0
+				|| strncmp(line, "backtrace", 9)==0) {
+			JSScript		*fs;
+			JSStackFrame	*fpi=NULL;
+			int				fnum=0;
+
+			while(JS_FrameIterator(cx, &fpi)) {
+				fs=JS_GetFrameScript(cx, fpi);
+				fname=JS_GetScriptFilename(cx, fs);
+				if(fname==NULL)
+					fname="No name";
+				fname=getfname(fname);
+				pc=JS_GetFramePC(cx, fpi);
+				fprintf(stdout, "%c#%-2u %p ",fpi==fp?'*':' ',fnum,pc);
+				fn=JS_GetFrameFunction(cx, fpi);
+				if(fn) {
+					JSString	*name;
+
+					name=JS_GetFunctionId(fn);
+					if(name) {
+						JSSTRING_TO_STRING(cx, name, cp, NULL);
+						fprintf(stdout, "in %s() ", cp);
+					}
+				}
+				fputs("at ",stdout);
+				fputs(fname, stdout);
+				if(pc)
+					fprintf(stdout, ":%u",JS_PCToLineNumber(cx, fs, pc));
+				fputs("\n", stdout);
+				fnum++;
+			}
+			continue;
+		}
+		if(strncmp(line,"up", 2)==0) {
+			JSStackFrame	*fpn=fp;
+
+			JS_FrameIterator(cx, &fpn);
+			if(fpn==NULL) {
+				fputs("No frame above this one...\n",stderr);
+				continue;
+			}
+			fp=fpn;
+			script=JS_GetFrameScript(cx, fp);
+			continue;
+		}
+		if(strncmp(line,"down", 4)==0) {
+			JSStackFrame	*fpi=NULL;
+			JSStackFrame	*fpp=NULL;
+
+			while(JS_FrameIterator(cx, &fpi)) {
+				if(fpi==fp) {
+					if(fpp==NULL) {
+						fputs("Already at deepest stack frame\n",stderr);
+						break;
+					}
+					fp=fpp;
+					script=JS_GetFrameScript(cx, fp);
+					break;
+				}
+				fpp=fpi;
+			}
+			if(fpi==NULL)
+				fputs("A strange amd mysterious error occured!\n",stderr);
 			continue;
 		}
 		fputs("Unrecognized command:\n"
-			  "break ####       - Sets a breakpoint\n"
-			  "r                - Runs the script\n"
-			  "eval <statement> - eval() <statement> in the current frame\n"
-			  "e <statement>    - eval() <statement> in the current frame\n"
-			  "clear            - Clears pending exceptions (doesn't seem to help)\n"
+			  "break [file:]#### - Sets a breakpoint at line #### in file\n"
+			  "                    If no file is specified, uses the current one\n"
+			  "r                 - Runs the script\n"
+			  "eval <statement>  - eval() <statement> in the current frame\n"
+			  "e <statement>     - eval() <statement> in the current frame\n"
+			  "clear             - Clears pending exceptions (doesn't seem to help)\n"
+			  "bt                - Prints a backtrace\n"
+			  "backtrace         - Alias for bt\n"
+			  "up                - Move to the previous stack frame\n"
+			  "down              - Move to the next stack frame\n"
 			  "\n",stderr);
 	}
 	return DEBUG_CONTINUE;
@@ -869,7 +1098,7 @@ static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc
 
 	fputs("Breakpoint reached\n",stdout);
 
-    switch(debug_prompt(script)) {
+    switch(debug_prompt(cx, script)) {
 		case DEBUG_CONTINUE:
 			return JSTRAP_CONTINUE;
 		case DEBUG_EXIT:
@@ -884,7 +1113,7 @@ static JSTrapStatus throw_handler(JSContext *cx, JSScript *script, jsbytecode *p
 
 	fputs("Exception thrown\n",stdout);
 
-    switch(debug_prompt(script)) {
+    switch(debug_prompt(cx, script)) {
 		case DEBUG_CONTINUE:
 			return JSTRAP_CONTINUE;
 		case DEBUG_EXIT:
@@ -1003,6 +1232,8 @@ long js_exec(const char *fname, char** args)
 		fclose(fp);
 
 	start=xp_timer();
+	if(debugger)
+		init_debugger(js_runtime, js_cx);
 	if((js_script=JS_CompileScript(js_cx, js_glob, js_buf, js_buflen, fname==NULL ? NULL : path, 1))==NULL) {
 		lprintf(LOG_ERR,"!Error compiling script from %s",path);
 		return(-1);
@@ -1014,11 +1245,9 @@ long js_exec(const char *fname, char** args)
 
 	js_PrepareToExecute(js_cx, js_glob, fname==NULL ? NULL : path, orig_cwd);
 	start=xp_timer();
-	if((!debugger) || debug_prompt(JS_GetScriptFromObject(js_script))==DEBUG_CONTINUE) {
-		if(debugger)
-			JS_SetThrowHook(js_runtime, throw_handler, NULL);
-		JS_ExecuteScript(js_cx, js_glob, js_script, &rval);
-	}
+	if(debugger)
+		debug_prompt(js_cx, JS_GetScriptFromObject(js_script));
+	JS_ExecuteScript(js_cx, js_glob, js_script, &rval);
 	JS_GetProperty(js_cx, js_glob, "exit_code", &rval);
 	if(rval!=JSVAL_VOID && JSVAL_IS_NUMBER(rval)) {
 		char	*p;
@@ -1319,8 +1548,6 @@ int main(int argc, char **argv, char** environ)
 		}
 		fprintf(statfp,"\n");
 
-		if(debugger)
-			JS_SetDebugMode(js_cx, JS_TRUE);
 		result=js_exec(module,&argv[argn]);
 		JS_RemoveObjectRoot(js_cx, &js_glob);
 		JS_ENDREQUEST(js_cx);
-- 
GitLab