js_internal.c 15.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
/* js_internal.c */

/* Synchronet "js" object, for internal JavaScript branch and GC control */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 2011 Rob Swindell - http://www.synchro.net/copyright.html		*
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * 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.	*
 ****************************************************************************/

#include "sbbs.h"
39
#include "js_request.h"
40

41
42
43
#ifdef _DEBUG
	#include <jscntxt.h>	/* Needed for Context-private data structure */
#endif
44
45

enum {
46
47
	 PROP_VERSION
	,PROP_TERMINATED
48
	,PROP_AUTO_TERMINATE
49
	,PROP_BRANCH_COUNTER
50
51
52
	,PROP_BRANCH_LIMIT
	,PROP_YIELD_INTERVAL
	,PROP_GC_INTERVAL
53
	,PROP_GC_ATTEMPTS
54
55
#ifdef jscntxt_h___
	,PROP_GC_COUNTER
56
57
58
	,PROP_GC_LASTBYTES
	,PROP_BYTES
	,PROP_MAXBYTES
59
#endif
60
	,PROP_GLOBAL
61
62
63
64
65
66
67
68
69
70
71
72
73
};

static JSBool js_get(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    jsint			tiny;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

    tiny = JSVAL_TO_INT(id);

	switch(tiny) {
74
75
76
		case PROP_VERSION:
			*vp=STRING_TO_JSVAL(JS_NewStringCopyZ(cx,(char *)JS_GetImplementationVersion()));
			break;
77
		case PROP_TERMINATED:
78
79
80
			if(branch->terminated==NULL)
				*vp=JSVAL_FALSE;
			else
81
82
				*vp=BOOLEAN_TO_JSVAL(*branch->terminated);
			break;
83
84
85
		case PROP_AUTO_TERMINATE:
			*vp=BOOLEAN_TO_JSVAL(branch->auto_terminate);
			break;
86
87
88
89
90
91
92
		case PROP_BRANCH_COUNTER:
			JS_NewNumberValue(cx,branch->counter,vp);
			break;
		case PROP_BRANCH_LIMIT:
			JS_NewNumberValue(cx,branch->limit,vp);
			break;
		case PROP_YIELD_INTERVAL:
93
			JS_NewNumberValue(cx,branch->yield_interval,vp);
94
95
			break;
		case PROP_GC_INTERVAL:
96
			JS_NewNumberValue(cx,branch->gc_interval,vp);
97
			break;
98
99
100
		case PROP_GC_ATTEMPTS:
			JS_NewNumberValue(cx,branch->gc_attempts,vp);
			break;
101
#ifdef jscntxt_h___
102
103
		case PROP_GC_COUNTER:
			JS_NewNumberValue(cx,cx->runtime->gcNumber,vp);
104
105
106
107
			break;
		case PROP_GC_LASTBYTES:
			JS_NewNumberValue(cx,cx->runtime->gcLastBytes,vp);
			break;
108
109
		case PROP_BYTES:
			JS_NewNumberValue(cx,cx->runtime->gcBytes,vp);
110
			break;
111
112
		case PROP_MAXBYTES:
			JS_NewNumberValue(cx,cx->runtime->gcMaxBytes,vp);
113
			break;
114
#endif
115
		case PROP_GLOBAL:
116
			*vp = OBJECT_TO_JSVAL(JS_GetGlobalObject(cx));	
117
			break;
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
	}

	return(JS_TRUE);
}

static JSBool js_set(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
{
    jsint			tiny;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

    tiny = JSVAL_TO_INT(id);

	switch(tiny) {
134
135
		case PROP_TERMINATED:
			if(branch->terminated!=NULL)
136
				JS_ValueToBoolean(cx, *vp, (int *)branch->terminated);
137
			break;
138
139
140
		case PROP_AUTO_TERMINATE:
			JS_ValueToBoolean(cx,*vp,&branch->auto_terminate);
			break;
141
142
143
144
145
146
147
		case PROP_BRANCH_COUNTER:
			JS_ValueToInt32(cx, *vp, (int32*)&branch->counter);
			break;
		case PROP_BRANCH_LIMIT:
			JS_ValueToInt32(cx, *vp, (int32*)&branch->limit);
			break;
		case PROP_GC_INTERVAL:
148
			JS_ValueToInt32(cx, *vp, (int32*)&branch->gc_interval);
149
150
			break;
		case PROP_YIELD_INTERVAL:
151
			JS_ValueToInt32(cx, *vp, (int32*)&branch->yield_interval);
152
			break;
153
#ifdef jscntxt_h___
154
155
156
		case PROP_MAXBYTES:
			JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes);
			break;
157
#endif
158
159
	}

160
	return(JS_TRUE);
161
162
}

163
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
164

165
166
167
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

168
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
169
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
170
171
172
173
174
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
	{	"branch_counter",	PROP_BRANCH_COUNTER,JSPROP_ENUMERATE,	311 },
	{	"branch_limit",		PROP_BRANCH_LIMIT,	JSPROP_ENUMERATE,	311 },
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
175
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
176
#ifdef jscntxt_h___
177
178
179
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
180
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
181
#endif
rswindell's avatar
rswindell committed
182
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
183
184
185
	{0}
};

186
#ifdef BUILD_JSDOCS
187
static char* prop_desc[] = {
188
189
	 "JavaScript engine version information (AKA system.js_version)"
	,"set to <i>false</i> to disable the automatic termination of the script upon external request"
190
	,"termination has been requested (stop execution as soon as possible)"
191
	,"number of branch operations performed in this runtime"
192
193
	,"maximum number of branches, used for infinite-loop detection (0=disabled)"
	,"interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)"
194
	,"interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)"
195
	,"number of garbage collections attempted in this runtime - <small>READ ONLY</small>"
196
#ifdef jscntxt_h___
197
198
199
	,"number of garbage collections performed in this runtime - <small>READ ONLY</small>"
	,"number of heap bytes in use after last garbage collection - <small>READ ONLY</small>"
	,"number of heap bytes currently in use - <small>READ ONLY</small>"
200
	,"maximum number of bytes available for heap"
201
#endif
202
	,"global (top level) object - <small>READ ONLY</small>"
203
204
205
206
	,NULL
};
#endif

207
JSBool DLLCALL
208
js_CommonBranchCallback(JSContext *cx, js_branch_t* branch)
209
210
211
{
	branch->counter++;

212
213
214
	/* Terminated? */
	if(branch->auto_terminate &&
		(branch->terminated!=NULL && *branch->terminated)) {
215
		JS_ReportWarning(cx,"Terminated");
216
217
218
219
		branch->counter=0;
		return(JS_FALSE);
	}

220
221
222
223
224
225
	/* Infinite loop? */
	if(branch->limit && branch->counter > branch->limit) {
		JS_ReportError(cx,"Infinite loop (%lu branches) detected",branch->counter);
		branch->counter=0;
		return(JS_FALSE);
	}
226

227
#ifndef USE_JS_OPERATION_CALLBACK
228
	/* Give up timeslices every once in a while */
deuce's avatar
deuce committed
229
230
231
	if(branch->yield_interval && (branch->counter%branch->yield_interval)==0) {
		jsrefcount	rc;

232
		rc=JS_SUSPENDREQUEST(cx);
233
		YIELD();
234
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
235
	}
236

237
	/* Periodic Garbage Collection */
238
239
	if(branch->gc_interval && (branch->counter%branch->gc_interval)==0)
		JS_MaybeGC(cx), branch->gc_attempts++;
240
#endif
241
242
243
244

    return(JS_TRUE);
}

245
246
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
247
js_eval(JSContext *parent_cx, JSObject *parent_obj, uintN argc, jsval *argv, jsval *rval)
248
{
249
	char*			buf;
250
251
	size_t			buflen;
	JSString*		str;
252
    JSScript*		script;
253
254
	JSContext*		cx;
	JSObject*		obj;
255
	JSErrorReporter	reporter;
256
#ifndef EVAL_BRANCH_CALLBACK
257
#ifndef USE_JS_OPERATION_CALLBACK
258
	JSBranchCallback callback;
259
#endif
260
#endif
261

262
263
264
	if(argc<1)
		return(JS_TRUE);

265
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
266
		return(JS_FALSE);
267
268
269
	if((buf=JS_GetStringBytes(str))==NULL)
		return(JS_FALSE);
	buflen=JS_GetStringLength(str);
270

271
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL)
272
273
		return(JS_FALSE);

274
	/* Use the error reporter from the parent context */
275
276
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
277
	JS_SetErrorReporter(cx,reporter);
278

279
#ifdef EVAL_BRANCH_CALLBACK
280
	JS_SetContextPrivate(cx, JS_GetPrivate(parent_cx, parent_obj));
281
#if JS_VERSION>180
282
283
	JS_SetOperationCallback(cx, js_OperationCallback);
#else
284
	JS_SetBranchCallback(cx, js_BranchCallback);
285
#endif
286
287
#else	/* Use the branch callback from the parent context */
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
288
#if JS_VERSION>180
deuce's avatar
deuce committed
289
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
290
#else
291
292
293
	callback=JS_SetBranchCallback(parent_cx,NULL);
	JS_SetBranchCallback(parent_cx, callback);
	JS_SetBranchCallback(cx, callback);
294
#endif
295
#endif
296

297
298
299
300
301
	if((obj=JS_NewObject(cx, NULL, NULL, NULL))==NULL
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
		return(JS_FALSE);
	}
302

303
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
304
305
306
307
308
309
310
311
312
		JS_ExecuteScript(cx, obj, script, rval);
		JS_DestroyScript(cx, script);
	}

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

313
314
315
static JSBool
js_gc(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
316
317
318
319
320
	JSBool			forced=JS_TRUE;
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);
321
322
323
324
325
326
327
328
329

	if(argc)
		JS_ValueToBoolean(cx,argv[0],&forced);

	if(forced)
		JS_GC(cx);
	else
		JS_MaybeGC(cx);

330
331
	branch->gc_attempts++;

332
333
334
	return(JS_TRUE);
}

335
336
337
338
339
340
341
342
343
344
static JSBool
js_report_error(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JS_ReportError(cx,"%s",JS_GetStringBytes(JS_ValueToString(cx, argv[0])));

	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
345

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
static JSBool
js_on_exit(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	js_branch_t*	branch;

	if((branch=(js_branch_t*)JS_GetPrivate(cx,obj))==NULL)
		return(JS_FALSE);

	if(branch->exit_func==NULL)
		branch->exit_func=strListInit();

	strListPush(&branch->exit_func,JS_GetStringBytes(JS_ValueToString(cx, argv[0])));

	return(JS_TRUE);
}

362
363
364
365
366
367
368
369
370
371
372
373
374
375
static JSBool
js_get_parent(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
{
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
		*rval = OBJECT_TO_JSVAL(parent);

	return(JS_TRUE);
}

376
static jsSyncMethodSpec js_functions[] = {
rswindell's avatar
rswindell committed
377
	{"eval",            js_eval,            0,	JSTYPE_UNDEF,	JSDOCSTR("script")
378
	,JSDOCSTR("evaluate a JavaScript string in its own (secure) context, returning the result")
379
	,311
380
	},		
rswindell's avatar
rswindell committed
381
	{"gc",				js_gc,				0,	JSTYPE_VOID,	JSDOCSTR("forced=<tt>true</tt>")
382
383
384
	,JSDOCSTR("perform a garbage collection operation (freeing memory for unused allocated objects), "
		"if <i>forced</i> is <i>true</i> (the default) a garbage collection is always performed, "
		"otherwise it is only performed if deemed appropriate by the JavaScript engine")
385
	,311
386
	},
rswindell's avatar
rswindell committed
387
388
	{"on_exit",			js_on_exit,			1,	JSTYPE_VOID,	JSDOCSTR("to_eval")
	,JSDOCSTR("add a string to evaluate/execute (LIFO stack) upon script's termination")
389
	,313
390
	},
rswindell's avatar
rswindell committed
391
	{"report_error",	js_report_error,	1,	JSTYPE_VOID,	JSDOCSTR("error [,fatal=<tt>false</tt>]")
392
393
	,JSDOCSTR("report an error using the standard JavaScript error reporting mechanism "
	"(including script filename and line number), "
394
395
	"if <i>fatal</i> is <i>true</i>, immediately terminates script")
	,313
396
	},
rswindell's avatar
rswindell committed
397
	{"get_parent",		js_get_parent,		1,	JSTYPE_OBJECT,	JSDOCSTR("object")
398
	,JSDOCSTR("return the parent of the specified object")
rswindell's avatar
rswindell committed
399
	,314
400
	},
401
402
403
	{0}
};

deuce's avatar
deuce committed
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsval id)
{
	char*			name=NULL;

	if(id != JSVAL_NULL)
		name=JS_GetStringBytes(JSVAL_TO_STRING(id));

	return(js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0));
}

static JSBool js_internal_enumerate(JSContext *cx, JSObject *obj)
{
	return(js_internal_resolve(cx, obj, JSVAL_NULL));
}

static JSClass js_internal_class = {
     "JsInternal"				/* name			*/
    ,JSCLASS_HAS_PRIVATE	/* flags		*/
	,JS_PropertyStub		/* addProperty	*/
	,JS_PropertyStub		/* delProperty	*/
	,js_get					/* getProperty	*/
	,js_set					/* setProperty	*/
	,js_internal_enumerate	/* enumerate	*/
	,js_internal_resolve	/* resolve		*/
	,JS_ConvertStub			/* convert		*/
	,JS_FinalizeStub		/* finalize		*/
};

432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#if JS_VERSION >= 185
char* DLLCALL js_getstring(JSContext *cx, JSString *str)
{
	size_t			len;
	const jschar	*val;
	char			*ret;

	if(!(val=JS_GetStringCharsAndLength(cx, str, &len)))
		return NULL;
	if(!(ret=malloc(len+1))
		return NULL;
	memcpy(ret, val, len);
	ret[len]=0;
	return ret;
}
#endif

449
450
451
452
453
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_branch_t* branch)
{
	char*	p;
	jsval	rval;
	JSScript* script;
454
	BOOL	auto_terminate=branch->auto_terminate;
455

rswindell's avatar
rswindell committed
456
	branch->auto_terminate=FALSE;
457

458
459
460
461
462
	while((p=strListPop(&branch->exit_func))!=NULL) {
		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
			JS_ExecuteScript(cx, obj, script, &rval);
			JS_DestroyScript(cx, script);
		}
deuce's avatar
deuce committed
463
		free(p);
464
465
466
	}

	strListFree(&branch->exit_func);
467

468
469
	if(auto_terminate)
		branch->auto_terminate = TRUE;
470
471
}

472
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_branch_t* branch, js_startup_t* startup)
473
474
475
476
477
478
479
480
481
482
{
	JSObject*	obj;

	if((obj = JS_DefineObject(cx, parent, "js", &js_internal_class, NULL
		,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
		return(NULL);

	if(!JS_SetPrivate(cx, obj, branch))	/* Store a pointer to js_branch_t */
		return(NULL);

483
484
485
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
486
		str_list_t	load_path;
487
488
489
490
491
492
493

		if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) 
			return(NULL);
		val=OBJECT_TO_JSVAL(load_path_list);
		if(!JS_SetProperty(cx, obj, JAVASCRIPT_LOAD_PATH_LIST, &val)) 
			return(NULL);

494
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
495
496
497
			JSString*	js_str;
			unsigned	i;

498
499
500
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
501
502
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
503
					break;
504
			}
505
			strListFree(&load_path);
506
507
508
		}
	}

509
#ifdef BUILD_JSDOCS
510
	js_DescribeSyncObject(cx,obj,"JavaScript execution and garbage collection control object",311);
511
512
513
514
515
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
516

517
void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir)
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
{
	JSString*	str;
	jsval		val;

	if(JS_GetProperty(cx, obj, "js", &val) && JSVAL_IS_OBJECT(val)) {
		JSObject* js = JSVAL_TO_OBJECT(val);
		char	dir[MAX_PATH+1];

		if(filename!=NULL) {
			char* p;

			if((str=JS_NewStringCopyZ(cx, filename)) != NULL)
				JS_DefineProperty(cx, js, "exec_path", STRING_TO_JSVAL(str)
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
			if((str=JS_NewStringCopyZ(cx, getfname(filename))) != NULL)
				JS_DefineProperty(cx, js, "exec_file", STRING_TO_JSVAL(str)
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
			SAFECOPY(dir,filename);
			p=getfname(dir);
			*p=0;
			if((str=JS_NewStringCopyZ(cx, dir)) != NULL)
				JS_DefineProperty(cx, js, "exec_dir", STRING_TO_JSVAL(str)
					,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
		}
542
543
544
545
546
		if(startup_dir==NULL)
			startup_dir="";
		if((str=JS_NewStringCopyZ(cx, startup_dir)) != NULL)
			JS_DefineProperty(cx, js, "startup_dir", STRING_TO_JSVAL(str)
				,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
547
548
	}
}