From 2c7f3bc699f32ec7f017b82e7f2ae8bf03c6e893 Mon Sep 17 00:00:00 2001
From: Rob Swindell <rob@synchro.net>
Date: Sat, 6 Mar 2021 13:49:04 -0800
Subject: [PATCH] Allow fine-grained control over JavaScript compiler options
 via *.ini
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

JavaScriptOptions bit-field can be set in sbbs.ini and jsexec.ini to over-ride the default JS compiler options which have been changed from 0 to (options previously only used by JSDoor):
JIT | METHODJIT | COMPILE_N_GO | PROFILING

* JIT - TraceMonkey
* METHODJIT - JägerMonkey
* COMPILE_N_GO - compile-time scope chain resolution of consts
* PROFILING - Choose between TraceMonkey and JägerMonkey at compile-time based on profiling results

Other options available but not enabled by default:
* STRICT - warn on debious practice (i.e. similar to "use strict")
* WERROR - convert warnings to errors
* VAROBJFIX -  use last object on scope chain as the ECMA 'variables object'
* RELIMIT - Throw exception on any regular expression which backtracks more than n^3 times, where n is length of the input string
* ANONFUNFIX - Disallow function () {} in statement context per ECMA-262 Edition 3.
* METHODJIT_ALWAYS - Always whole-method JIT, don't tune at run-time.

Also:
- Fixed JS warning string formatting (missing space separator).
- Removed an extraneous new-line in lprintf() call in mailsrvr.
- Added basic assertEq() global method to jsexec, required when running SpiderMonkey test scripts.
---
 src/sbbs3/js_global.c | 16 +---------------
 src/sbbs3/jsexec.c    | 33 +++++++++++++++++++++++++++++----
 src/sbbs3/mailsrvr.c  |  3 ++-
 src/sbbs3/main.cpp    |  5 +++--
 src/sbbs3/sbbs_ini.c  |  4 ++++
 src/sbbs3/sbbsdefs.h  |  1 +
 src/sbbs3/services.c  |  1 +
 src/sbbs3/startup.h   | 17 +++++++++++++++++
 src/sbbs3/websrvr.c   |  4 +++-
 9 files changed, 61 insertions(+), 23 deletions(-)

diff --git a/src/sbbs3/js_global.c b/src/sbbs3/js_global.c
index 9384c900c4..1401886058 100644
--- a/src/sbbs3/js_global.c
+++ b/src/sbbs3/js_global.c
@@ -1,8 +1,5 @@
 /* Synchronet JavaScript "global" object properties/methods for all servers */
 
-/* $Id: js_global.c,v 1.409 2020/08/09 01:53:52 rswindell Exp $ */
-// vi: tabstop=4
-
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
@@ -16,21 +13,9 @@
  * See the GNU General Public License for more details: gpl.txt or			*
  * http://www.fsf.org/copyleft/gpl.html										*
  *																			*
- * Anonymous FTP access to the most recent released source is available at	*
- * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
- *																			*
- * Anonymous CVS access to the development source and modification history	*
- * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
- *     (just hit return, no password is necessary)							*
- * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
- *																			*
  * For Synchronet coding style and modification guidelines, see				*
  * http://www.synchro.net/source.html										*
  *																			*
- * You are encouraged to submit any modifications (preferably in Unix diff	*
- * format) via e-mail to mods@synchro.net									*
- *												
  * Note: If this box doesn't appear square, then you need to fix your tabs.	*
  ****************************************************************************/
 
@@ -333,6 +318,7 @@ js_load(JSContext *cx, uintN argc, jsval *arglist)
 			JS_RESUMEREQUEST(cx, rc);
 			return(JS_FALSE);
 		}
+		JS_SetOptions(bg->cx, p->startup->options);
 		JS_BEGINREQUEST(bg->cx);
 
 		if(!js_CreateCommonObjects(bg->cx
diff --git a/src/sbbs3/jsexec.c b/src/sbbs3/jsexec.c
index 51c7c82c2b..5f2dd6e778 100644
--- a/src/sbbs3/jsexec.c
+++ b/src/sbbs3/jsexec.c
@@ -29,6 +29,7 @@
 #include <termios.h>
 #endif
 
+#define STARTUP_INI_BITDESC_TABLES
 #include "sbbs.h"
 #include "ciolib.h"
 #include "ini_file.h"
@@ -45,6 +46,7 @@ static const char*	strJavaScriptMaxBytes		="JavaScriptMaxBytes";
 static const char*	strJavaScriptTimeLimit		="JavaScriptTimeLimit";
 static const char*	strJavaScriptGcInterval		="JavaScriptGcInterval";
 static const char*	strJavaScriptYieldInterval	="JavaScriptYieldInterval";
+static const char*	strJavaScriptOptions		="JavaScriptOptions";
 
 js_startup_t	startup;
 JSRuntime*	js_runtime;
@@ -54,6 +56,7 @@ js_callback_t	cb;
 scfg_t		scfg;
 char*		text[TOTAL_TEXT];
 ulong		js_max_bytes=JAVASCRIPT_MAX_BYTES;
+ulong		js_opts = JAVASCRIPT_OPTIONS;
 FILE*		confp;
 FILE*		errfp;
 FILE*		nulfp;
@@ -700,6 +703,28 @@ js_putenv(JSContext *cx, uintN argc, jsval *arglist)
 	return(JS_TRUE);
 }
 
+// Forked from mozilla/js/src/shell/js.cpp: AssertEq()
+static JSBool
+js_AssertEq(JSContext *cx, uintN argc, jsval *vp)
+{
+    if (!(argc == 2 || (argc == 3 && JSVAL_IS_STRING(JS_ARGV(cx, vp)[2])))) {
+        JS_ReportError(cx, "assertEq: missing or invalid args");
+        return JS_FALSE;
+    }
+
+    jsval *argv = JS_ARGV(cx, vp);
+    JSBool same;
+    if (!JS_SameValue(cx, argv[0], argv[1], &same))
+        return JS_FALSE;
+    if (!same) {
+		JS_ReportError(cx, "not equal");
+        return JS_FALSE;
+    }
+    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+    return JS_TRUE;
+}
+
+
 static jsSyncMethodSpec js_global_functions[] = {
 	{"log",				js_log,				1},
 	{"read",			js_read,            1},
@@ -710,13 +735,14 @@ static jsSyncMethodSpec js_global_functions[] = {
     {"write",           js_write,           0},
     {"writeln",         js_writeln,         0},
     {"print",           js_writeln,         0},
-    {"printf",          jse_printf,          1},	
+    {"printf",          jse_printf,         1},	
 	{"alert",			js_alert,			1},
 	{"prompt",			js_prompt,			1},
 	{"confirm",			js_confirm,			1},
 	{"deny",			js_deny,			1},
 	{"chdir",			js_chdir,			1},
 	{"putenv",			js_putenv,			1},
+	{"assertEq",		js_AssertEq,		2},
     {0}
 };
 
@@ -817,9 +843,7 @@ static BOOL js_init(char** env)
 
     if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
 		return(FALSE);
-#ifdef JSDOOR
-	JS_SetOptions(js_cx, JSOPTION_JIT | JSOPTION_METHODJIT | JSOPTION_COMPILE_N_GO | JSOPTION_PROFILING);
-#endif
+	JS_SetOptions(js_cx, js_opts);
 	JS_BEGINREQUEST(js_cx);
 
 	JS_SetErrorReporter(js_cx, js_ErrorReporter);
@@ -1138,6 +1162,7 @@ void get_ini_values(str_list_t ini, const char* section, js_callback_t* cb)
 	cb->gc_interval		= iniGetInteger(ini, section, strJavaScriptGcInterval		, cb->gc_interval);
 	cb->yield_interval	= iniGetInteger(ini, section, strJavaScriptYieldInterval	, cb->yield_interval);
 	cb->auto_terminate	= iniGetBool(ini, section, "AutoTerminate"					, cb->auto_terminate);
+	js_opts				= iniGetBitField(ini, section, strJavaScriptOptions			, js_options, js_opts);
 }
 
 /*********************/
diff --git a/src/sbbs3/mailsrvr.c b/src/sbbs3/mailsrvr.c
index f2eef48ad2..7b2b95cb08 100644
--- a/src/sbbs3/mailsrvr.c
+++ b/src/sbbs3/mailsrvr.c
@@ -2257,7 +2257,7 @@ js_mailproc(SOCKET sock, client_t* client, user_t* user, struct mailproc* mailpr
 	*result = 0;
 	do {
 		if(*js_runtime==NULL) {
-			lprintf(LOG_DEBUG,"%04d %s JavaScript: Creating runtime: %lu bytes\n"
+			lprintf(LOG_DEBUG,"%04d %s JavaScript: Creating runtime: %lu bytes"
 				,sock, log_prefix, startup->js.max_bytes);
 
 			if((*js_runtime = jsrt_GetNew(startup->js.max_bytes, 1000, __FILE__, __LINE__))==NULL)
@@ -2267,6 +2267,7 @@ js_mailproc(SOCKET sock, client_t* client, user_t* user, struct mailproc* mailpr
 		if(*js_cx==NULL) {
 			if((*js_cx = JS_NewContext(*js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
 				return FALSE;
+			JS_SetOptions(*js_cx, startup->js.options);
 		}
 		JS_BEGINREQUEST(*js_cx);
 
diff --git a/src/sbbs3/main.cpp b/src/sbbs3/main.cpp
index 0f733712d4..a462f3ee8e 100644
--- a/src/sbbs3/main.cpp
+++ b/src/sbbs3/main.cpp
@@ -1247,9 +1247,9 @@ js_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
 
 	if(JSREPORT_IS_WARNING(report->flags)) {
 		if(JSREPORT_IS_STRICT(report->flags))
-			warning="strict warning";
+			warning="strict warning ";
 		else
-			warning="warning";
+			warning="warning ";
 		log_level = LOG_WARNING;
 	} else {
 		warning=nulstr;
@@ -1276,6 +1276,7 @@ JSContext* sbbs_t::js_init(JSRuntime** runtime, JSObject** glob, const char* des
 
     if((js_cx = JS_NewContext(*runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
 		return NULL;
+	JS_SetOptions(js_cx, startup->js.options);
 	JS_BEGINREQUEST(js_cx);
 
 	memset(&js_callback,0,sizeof(js_callback));
diff --git a/src/sbbs3/sbbs_ini.c b/src/sbbs3/sbbs_ini.c
index 74184c2368..24765d3330 100644
--- a/src/sbbs3/sbbs_ini.c
+++ b/src/sbbs3/sbbs_ini.c
@@ -58,6 +58,7 @@ static const char*	strJavaScriptTimeLimit		="JavaScriptTimeLimit";
 static const char*	strJavaScriptGcInterval		="JavaScriptGcInterval";
 static const char*	strJavaScriptYieldInterval	="JavaScriptYieldInterval";
 static const char*	strJavaScriptLoadPath		="JavaScriptLoadPath";
+static const char*	strJavaScriptOptions		="JavaScriptOptions";
 static const char*	strSemFileCheckFrequency	="SemFileCheckFrequency";
 
 #define DEFAULT_LOG_LEVEL				LOG_DEBUG
@@ -104,6 +105,7 @@ void sbbs_get_js_settings(
 	js->time_limit		= iniGetInteger(list,section,strJavaScriptTimeLimit		,defaults->time_limit);
 	js->gc_interval		= iniGetInteger(list,section,strJavaScriptGcInterval	,defaults->gc_interval);
 	js->yield_interval	= iniGetInteger(list,section,strJavaScriptYieldInterval	,defaults->yield_interval);
+	js->options			= iniGetBitField(list, section, strJavaScriptOptions	,js_options, defaults->options);
 
 	/* Get JavaScriptLoadPath, use default if key is missing, use blank if key value is blank */
     if((p=iniGetExistingString(list, section, strJavaScriptLoadPath, nulstr, value)) == NULL) {
@@ -128,6 +130,7 @@ BOOL sbbs_set_js_settings(
 			,JAVASCRIPT_TIME_LIMIT
 			,JAVASCRIPT_GC_INTERVAL
 			,JAVASCRIPT_YIELD_INTERVAL
+			,JAVASCRIPT_OPTIONS
             ,JAVASCRIPT_LOAD_PATH
 		};
 	SAFECOPY(global_defaults.load_path, JAVASCRIPT_LOAD_PATH);
@@ -226,6 +229,7 @@ static void get_ini_globals(str_list_t list, global_startup_t* global)
 	global->js.time_limit		= JAVASCRIPT_TIME_LIMIT;
 	global->js.gc_interval		= JAVASCRIPT_GC_INTERVAL;
 	global->js.yield_interval	= JAVASCRIPT_YIELD_INTERVAL;
+	global->js.options			= JAVASCRIPT_OPTIONS;
     SAFECOPY(global->js.load_path, JAVASCRIPT_LOAD_PATH);
 
 	/* Read .ini values here */
diff --git a/src/sbbs3/sbbsdefs.h b/src/sbbs3/sbbsdefs.h
index 290043f294..0742417fcf 100644
--- a/src/sbbs3/sbbsdefs.h
+++ b/src/sbbs3/sbbsdefs.h
@@ -66,6 +66,7 @@
 #define JAVASCRIPT_GC_INTERVAL		100
 #define JAVASCRIPT_LOAD_PATH		"load"
 #define JAVASCRIPT_LOAD_PATH_LIST	"load_path_list"
+#define JAVASCRIPT_OPTIONS			0xC810	// JSOPTION_JIT | JSOPTION_METHODJIT | JSOPTION_COMPILE_N_GO | JSOPTION_PROFILING
 
 struct js_callback;
 typedef struct js_callback {
diff --git a/src/sbbs3/services.c b/src/sbbs3/services.c
index bd8716e2e1..0efdd14f82 100644
--- a/src/sbbs3/services.c
+++ b/src/sbbs3/services.c
@@ -754,6 +754,7 @@ js_initcx(JSRuntime* js_runtime, SOCKET sock, service_client_t* service_client,
 
     if((js_cx = JS_NewContext(js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
 		return(NULL);
+	JS_SetOptions(js_cx, startup->js.options);
 	JS_BEGINREQUEST(js_cx);
 
     JS_SetErrorReporter(js_cx, js_ErrorReporter);
diff --git a/src/sbbs3/startup.h b/src/sbbs3/startup.h
index e4febaaa63..7f0c0498fb 100644
--- a/src/sbbs3/startup.h
+++ b/src/sbbs3/startup.h
@@ -38,6 +38,7 @@ typedef struct {
 	ulong	time_limit;		/* maximum number of ticks (for infinite loop detection) */
 	ulong	gc_interval;	/* number of ticks between garbage collection attempts */
 	ulong	yield_interval;	/* number of ticks between time-slice yields */
+	ulong	options;
 	char	load_path[INI_MAX_VALUE_LEN];	/* additional (comma-separated) directories to search for load()ed scripts */
 } js_startup_t;
 
@@ -211,6 +212,22 @@ static ini_bitdesc_t bbs_options[] = {
 	{ 0								,NULL					}
 };
 
+static ini_bitdesc_t js_options[] = {
+
+	{ 1<<0	,"STRICT"				},
+	{ 1<<1	,"WERROR"				},
+	{ 1<<2	,"VAROBJFIX"			},
+	{ 1<<4	,"COMPILE_N_GO"			},
+	{ 1<<9	,"RELIMIT"				},
+	{ 1<<10	,"ANONFUNFIX"			},
+	{ 1<<11	,"JIT"					},
+	{ 1<<14	,"METHODJIT"			},
+	{ 1<<15	,"PROFILING"			},
+	{ 1<<16	,"METHODJIT_ALWAYS"		},
+	/* terminator */										
+	{ 0								,NULL					}
+};
+
 #endif
 
 #ifdef __cplusplus
diff --git a/src/sbbs3/websrvr.c b/src/sbbs3/websrvr.c
index cbe35e5346..c3efaee5f0 100644
--- a/src/sbbs3/websrvr.c
+++ b/src/sbbs3/websrvr.c
@@ -5754,9 +5754,11 @@ js_initcx(http_session_t *session)
 
     if((js_cx = JS_NewContext(session->js_runtime, JAVASCRIPT_CONTEXT_STACK))==NULL)
 		return(NULL);
+	JS_SetOptions(js_cx, startup->js.options);
 	JS_BEGINREQUEST(js_cx);
 
-	lprintf(LOG_DEBUG,"%04d JavaScript: Context created",session->socket);
+	lprintf(LOG_DEBUG,"%04d JavaScript: Context created with options: %lx"
+		,session->socket, (long)startup->js.options);
 
     JS_SetErrorReporter(js_cx, js_ErrorReporter);
 
-- 
GitLab