js_internal.c 24.9 KB
Newer Older
1
2
/* js_internal.c */

3
/* Synchronet "js" object, for internal JavaScript callback and GC control */
4

5
/* $Id: js_internal.c,v 1.99 2020/03/29 23:40:57 rswindell Exp $ */
6
7
8
9
10

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
11
 * Copyright 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
/* SpiderMonkey: */
#include <jsdbgapi.h>

44
enum {
45
46
	 PROP_VERSION
	,PROP_TERMINATED
47
	,PROP_AUTO_TERMINATE
48
49
	,PROP_COUNTER
	,PROP_TIME_LIMIT
50
51
	,PROP_YIELD_INTERVAL
	,PROP_GC_INTERVAL
52
	,PROP_GC_ATTEMPTS
53
54
#ifdef jscntxt_h___
	,PROP_GC_COUNTER
55
56
57
	,PROP_GC_LASTBYTES
	,PROP_BYTES
	,PROP_MAXBYTES
58
#endif
59
	,PROP_GLOBAL
60
61
};

62
static JSBool js_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
63
{
64
	jsval idval;
65
    jsint			tiny;
66
	js_callback_t*	cb;
67
	js_callback_t*	top_cb;
68

69
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
70
71
		return(JS_FALSE);

72
73
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
74
75

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

	return(JS_TRUE);
}

131
static JSBool js_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
132
{
133
	jsval idval;
134
    jsint			tiny;
135
	js_callback_t*	cb;
136

137
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
138
139
		return(JS_FALSE);

140
141
    JS_IdToValue(cx, id, &idval);
    tiny = JSVAL_TO_INT(idval);
142
143

	switch(tiny) {
144
		case PROP_TERMINATED:
145
146
			if(cb->terminated!=NULL)
				JS_ValueToBoolean(cx, *vp, (int *)cb->terminated);
147
			break;
148
		case PROP_AUTO_TERMINATE:
149
			JS_ValueToBoolean(cx,*vp,&cb->auto_terminate);
150
			break;
151
152
		case PROP_COUNTER:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->counter))
153
				return JS_FALSE;
154
			break;
155
156
		case PROP_TIME_LIMIT:
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->limit))
157
				return JS_FALSE;
158
159
			break;
		case PROP_GC_INTERVAL:
160
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->gc_interval))
161
				return JS_FALSE;
162
163
			break;
		case PROP_YIELD_INTERVAL:
164
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cb->yield_interval))
165
				return JS_FALSE;
166
			break;
167
#ifdef jscntxt_h___
168
		case PROP_MAXBYTES:
169
170
			if(!JS_ValueToInt32(cx, *vp, (int32*)&cx->runtime->gcMaxBytes))
				return JS_FALSE;
171
			break;
172
#endif
173
174
	}

175
	return(JS_TRUE);
176
177
}

178
#define PROP_FLAGS	JSPROP_ENUMERATE|JSPROP_READONLY
179

180
181
182
static jsSyncPropertySpec js_properties[] = {
/*		 name,				tinyid,						flags,		ver	*/

183
	{	"version",			PROP_VERSION,		PROP_FLAGS,			311 },
184
	{	"auto_terminate",	PROP_AUTO_TERMINATE,JSPROP_ENUMERATE,	311 },
185
	{	"terminated",		PROP_TERMINATED,	JSPROP_ENUMERATE,	311 },
186
187
188
189
	{	"branch_counter",	PROP_COUNTER,		0,					311 },
	{	"counter",			PROP_COUNTER,		JSPROP_ENUMERATE,	316 },
	{	"branch_limit",		PROP_TIME_LIMIT,	0,					311 },
	{	"time_limit",		PROP_TIME_LIMIT,	JSPROP_ENUMERATE,	316 },
190
191
	{	"yield_interval",	PROP_YIELD_INTERVAL,JSPROP_ENUMERATE,	311 },
	{	"gc_interval",		PROP_GC_INTERVAL,	JSPROP_ENUMERATE,	311 },
192
	{	"gc_attempts",		PROP_GC_ATTEMPTS,	PROP_FLAGS,			311 },
193
#ifdef jscntxt_h___
194
195
196
	{	"gc_counter",		PROP_GC_COUNTER,	PROP_FLAGS,			311 },
	{	"gc_last_bytes",	PROP_GC_LASTBYTES,	PROP_FLAGS,			311 },
	{	"bytes",			PROP_BYTES,			PROP_FLAGS,			311 },
197
	{	"max_bytes",		PROP_MAXBYTES,		JSPROP_ENUMERATE,	311 },
198
#endif
rswindell's avatar
rswindell committed
199
	{	"global",			PROP_GLOBAL,		PROP_FLAGS,			314 },
200
201
202
	{0}
};

203
#ifdef BUILD_JSDOCS
204
static char* prop_desc[] = {
205
206
	 "JavaScript engine version information (AKA system.js_version)"
	,"set to <i>false</i> to disable the automatic termination of the script upon external request"
207
	,"termination has been requested (stop execution as soon as possible)"
208
209
	,"number of operation callbacks performed in this runtime"
	,"maximum number of operation callbacks, used for infinite-loop detection (0=disabled)"
210
	,"interval of periodic time-slice yields (lower number=higher frequency, 0=disabled)"
211
	,"interval of periodic garbage collection attempts (lower number=higher frequency, 0=disabled)"
212
	,"number of garbage collections attempted in this runtime - <small>READ ONLY</small>"
213
#ifdef jscntxt_h___
214
215
216
	,"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>"
217
	,"maximum number of bytes available for heap"
218
#endif
219
	,"global (top level) object - <small>READ ONLY</small>"
deuce's avatar
deuce committed
220
	/* New properties go here... */
221
	,"load() search path array.<br>For relative load paths (e.g. not beginning with '/' or '\\'), "
deuce's avatar
deuce committed
222
223
224
225
226
227
228
229
230
		"the path is assumed to be a sub-directory of the (configurable) mods or exec directories "
		"and is searched accordingly. "
		"So, by default, load(\"somefile.js\") will search in this order:<br>"
		"mods/load/somefile.js<br>"
		"exec/load/somefile.js<br>"
		"mods/somefile.js<br>"
		"exec/somefile.js<br>"
	,"full path and filename of JS file executed"
	,"JS filename executed (with no path)"
deuce's avatar
deuce committed
231
	,"directory of executed JS file"
232
	,"Either the configured startup directory in SCFG (for externals) or the cwd when jsexec is started"
deuce's avatar
deuce committed
233
	,"global scope for this script"
234
235
236
237
	,NULL
};
#endif

238
JSBool DLLCALL
239
js_CommonOperationCallback(JSContext *cx, js_callback_t* cb)
240
{
241
242
	js_callback_t *top_cb;

243
	cb->counter++;
244

245
	/* Terminated? */
246
247
248
249
250
251
252
253
	if(cb->auto_terminate) {
		for(top_cb=cb; top_cb; top_cb=top_cb->parent_cb) {
			if (top_cb->terminated!=NULL && *top_cb->terminated) {
				JS_ReportWarning(cx,"Terminated");
				cb->counter=0;
				return(JS_FALSE);
			}
		}
254
255
	}

256
	/* Infinite loop? */
257
	if(cb->limit && cb->counter > cb->limit) {
258
		JS_ReportError(cx,"Infinite loop (%lu operation callbacks) detected",cb->counter);
259
		cb->counter=0;
260
261
		return(JS_FALSE);
	}
262

263
	/* Give up timeslices every once in a while */
264
	if(cb->yield_interval && (cb->counter%cb->yield_interval)==0) {
deuce's avatar
deuce committed
265
266
		jsrefcount	rc;

267
		rc=JS_SUSPENDREQUEST(cx);
268
		YIELD();
269
		JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
270
	}
271

272
273
274
	/* Permit other contexts to run GC */
	JS_YieldRequest(cx);

275
	/* Periodic Garbage Collection */
276
277
	if(cb->gc_interval && (cb->counter%cb->gc_interval)==0)
		JS_MaybeGC(cx), cb->gc_attempts++;
278
279
280
281

    return(JS_TRUE);
}

deuce's avatar
deuce committed
282
283
284
285
286
287
288
289
// This is kind of halfway between js_execfile() in exec.cpp and js_load
static int
js_execfile(JSContext *cx, uintN argc, jsval *arglist)
{
	char*		cmd = NULL;
	size_t		cmdlen;
	size_t		pathlen;
	char*		startup_dir = NULL;
290
	uintN		arg=0;
deuce's avatar
deuce committed
291
	char		path[MAX_PATH+1] = "";
deuce's avatar
deuce committed
292
	JSObject*	scope = JS_GetScopeChain(cx);
293
294
	JSObject*	js_scope = NULL;
	JSObject*	pscope;
deuce's avatar
deuce committed
295
296
297
298
	JSObject*	js_script=NULL;
	JSObject*	nargv;
	jsval		rval;
	jsrefcount	rc;
299
	uintN		i;
deuce's avatar
deuce committed
300
	jsval		val;
deuce's avatar
deuce committed
301
302
	JSObject *js_obj;
	JSObject *pjs_obj;
303
	js_callback_t *	js_callback;
deuce's avatar
deuce committed
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334

	if(argc<1) {
		JS_ReportError(cx, "No filename passed");
		return(JS_FALSE);
	}

	jsval *argv=JS_ARGV(cx, arglist);

	if (!JSVAL_IS_STRING(argv[arg])) {
		JS_ReportError(cx, "Invalid script name");
		return(JS_FALSE);
	}
	JSVALUE_TO_MSTRING(cx, argv[arg++], cmd, NULL);
	HANDLE_PENDING(cx, cmd);
	if (cmd == NULL) {
		JS_ReportError(cx, "Invalid NULL string");
		return(JS_FALSE);
	}

	if (argc > arg) {
		if (JSVAL_IS_STRING(argv[arg])) {
			JSVALUE_TO_MSTRING(cx, argv[arg++], startup_dir, &cmdlen);
			HANDLE_PENDING(cx, cmd);
			if(startup_dir == NULL) {
				free(cmd);
				JS_ReportError(cx, "Invalid NULL string");
				return(JS_FALSE);
			}
		}
	}

335
	if (argc > arg && JSVAL_IS_OBJECT(argv[arg])) {
deuce's avatar
deuce committed
336
		js_scope = JSVAL_TO_OBJECT(argv[arg++]);
337
338
339
340
341
342
343
		if (js_scope == scope) {
			free(cmd);
			free(startup_dir);
			JS_ReportError(cx, "Invalid Scope");
			return(JS_FALSE);
		}
	}
deuce's avatar
deuce committed
344
	else {
345
346
		free(cmd);
		free(startup_dir);
deuce's avatar
deuce committed
347
348
349
350
		JS_ReportError(cx, "Invalid Scope");
		return(JS_FALSE);
	}

351
352
353
354
355
356
357
358
359
360
361
362
363
	pscope = scope;
	while ((!JS_GetProperty(cx, pscope, "js", &val) || val==JSVAL_VOID || !JSVAL_IS_OBJECT(val)) && pscope != NULL) {
		pscope = JS_GetParent(cx, pscope);
		if (pscope == NULL) {
			free(cmd);
			free(startup_dir);
			JS_ReportError(cx, "Walked to global, no js object!");
			return JS_FALSE;
		}
	}
	pjs_obj = JSVAL_TO_OBJECT(val);
	js_callback = JS_GetPrivate(cx, pjs_obj);

deuce's avatar
deuce committed
364
365
366
367
368
369
370
371
372
373
374
375
376
377
	if(isfullpath(cmd))
		SAFECOPY(path,cmd);
	else {
		// If startup dir specified, check there first.
		if (startup_dir) {
			SAFECOPY(path, startup_dir);
			backslash(path);
			strncat(path, cmd, sizeof(path) - strlen(path) - 1);
			rc=JS_SUSPENDREQUEST(cx);
			if (!fexist(path))
				*path = 0;
			JS_RESUMEREQUEST(cx, rc);
		}
		// Then check js.exec_dir
378
379
380
381
382
383
384
385
386
		/* if js.exec_dir is defined (location of executed script), search there first */
		if (*path == 0) {
			if(JS_GetProperty(cx, pjs_obj, "exec_dir", &val) && val!=JSVAL_VOID && JSVAL_IS_STRING(val)) {
				JSVALUE_TO_STRBUF(cx, val, path, sizeof(path), &pathlen);
				strncat(path, cmd, sizeof(path)-pathlen-1);
				rc=JS_SUSPENDREQUEST(cx);
				if(!fexistcase(path))
					path[0]=0;
				JS_RESUMEREQUEST(cx, rc);
deuce's avatar
deuce committed
387
388
389
390
391
392
			}
		}
	}
	free(cmd);

	if(!fexistcase(path)) {
393
		JS_ReportError(cx, "Script file (%s) does not exist", path);
deuce's avatar
deuce committed
394
395
396
397
398
399
400
401
402
		free(startup_dir);
		return JS_FALSE;
	}

	nargv=JS_NewArrayObject(cx, 0, NULL);

	JS_DefineProperty(cx, js_scope, "argv", OBJECT_TO_JSVAL(nargv)
		,NULL,NULL,JSPROP_READONLY|JSPROP_ENUMERATE);

403
404
405
406
407
	uintN nargc = 0;
	for(i=arg; i<argc; i++) {
		JS_SetElement(cx, nargv, nargc, &argv[i]);
		nargc++;
	}
deuce's avatar
deuce committed
408

409
	JS_DefineProperty(cx, js_scope, "argc", INT_TO_JSVAL(nargc)
deuce's avatar
deuce committed
410
411
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);

412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
	js_obj = js_CreateInternalJsObject(cx, js_scope, js_callback, NULL);
	js_PrepareToExecute(cx, js_scope, path, startup_dir, js_scope);
	free(startup_dir);
	// Copy in the load_path_list...
	if(pjs_obj != NULL) {
		JSObject*	pload_path_list;
		JSObject*	load_path_list;
		uint32_t	plen;
		uint32_t	pcnt;

		if (JS_GetProperty(cx, pjs_obj, JAVASCRIPT_LOAD_PATH_LIST, &val) && val!=JSVAL_VOID && JSVAL_IS_OBJECT(val)) {
			pload_path_list = JSVAL_TO_OBJECT(val);
			if (!JS_IsArrayObject(cx, pload_path_list)) {
				JS_ReportError(cx, "Weird js."JAVASCRIPT_LOAD_PATH_LIST" value");
				return JS_FALSE;
			}
			if((load_path_list=JS_NewArrayObject(cx, 0, NULL))==NULL) {
				JS_ReportError(cx, "Unable to create js."JAVASCRIPT_LOAD_PATH_LIST);
				return JS_FALSE;
			}
			val = OBJECT_TO_JSVAL(load_path_list);
			JS_SetProperty(cx, js_obj, JAVASCRIPT_LOAD_PATH_LIST, &val);
			JS_GetArrayLength(cx, pload_path_list, &plen);
			for (pcnt = 0; pcnt < plen; pcnt++) {
				JS_GetElement(cx, pload_path_list, pcnt, &val);
				JS_SetElement(cx, load_path_list, pcnt, &val);
			}
		}
		else {
			JS_ReportError(cx, "Unable to get parent js."JAVASCRIPT_LOAD_PATH_LIST" array.");
			return JS_FALSE;
		}
	}
	else {
		JS_ReportError(cx, "Unable to get parent js object");
		return JS_FALSE;
	}

deuce's avatar
deuce committed
450
451
452
	js_script=JS_CompileFile(cx, js_scope, path);

	if(js_script == NULL) {
453
454
455
456
457
		/* If the script fails to compile, it's not a fatal error
		 * for the caller. */
		if (JS_IsExceptionPending(cx)) {
			JS_GetPendingException(cx, &rval);
			JS_SET_RVAL(cx, arglist, rval);
deuce's avatar
deuce committed
458
		}
459
460
		JS_ClearPendingException(cx);
		return(JS_TRUE);
deuce's avatar
deuce committed
461
462
463
	}

	JS_ExecuteScript(cx, js_scope, js_script, &rval);
464
465
466
467
	if (JS_IsExceptionPending(cx)) {
		JS_GetPendingException(cx, &rval);
	}
	else {
deuce's avatar
deuce committed
468
469
		JS_GetProperty(cx, js_scope, "exit_code", &rval);
	}
470
471
472
473
	JS_SET_RVAL(cx, arglist, rval);
	JS_ClearPendingException(cx);

	js_EvalOnExit(cx, js_scope, js_callback);
deuce's avatar
deuce committed
474
475
	JS_ReportPendingException(cx);
	JS_DestroyScript(cx, js_script);
476
	JS_GC(cx);
deuce's avatar
deuce committed
477
478
479
480

	return JS_TRUE;
}

481
482
483
484
485
486
487
488
static JSClass eval_class = {
    "Global",  /* name */
    JSCLASS_GLOBAL_FLAGS,  /* flags */
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

489
490
/* Execute a string in its own context (away from Synchronet objects) */
static JSBool
491
js_eval(JSContext *parent_cx, uintN argc, jsval *arglist)
492
{
493
	jsval *argv=JS_ARGV(parent_cx, arglist);
494
	char*			buf = NULL;
495
496
	size_t			buflen;
	JSString*		str;
497
    JSObject*		script;
498
499
	JSContext*		cx;
	JSObject*		obj;
500
	JSErrorReporter	reporter;
501

502
503
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

504
505
506
	if(argc<1)
		return(JS_TRUE);

507
	if((str=JS_ValueToString(parent_cx, argv[0]))==NULL)
508
		return(JS_FALSE);
509
	JSSTRING_TO_MSTRING(parent_cx, str, buf, &buflen);
510
	HANDLE_PENDING(parent_cx, buf);
deuce's avatar
deuce committed
511
	if(buf==NULL)
512
		return(JS_TRUE);
513

514
515
	if((cx=JS_NewContext(JS_GetRuntime(parent_cx),JAVASCRIPT_CONTEXT_STACK))==NULL) {
		free(buf);
516
		return(JS_FALSE);
517
	}
518

519
	/* Use the error reporter from the parent context */
520
521
	reporter=JS_SetErrorReporter(parent_cx,NULL);
	JS_SetErrorReporter(parent_cx,reporter);
522
	JS_SetErrorReporter(cx,reporter);
523

524
	/* Use the operation callback from the parent context */
525
	JS_SetContextPrivate(cx, JS_GetContextPrivate(parent_cx));
deuce's avatar
deuce committed
526
	JS_SetOperationCallback(cx, JS_GetOperationCallback(parent_cx));
527

528
	if((obj=JS_NewCompartmentAndGlobalObject(cx, &eval_class, NULL))==NULL
529
530
		|| !JS_InitStandardClasses(cx,obj)) {
		JS_DestroyContext(cx);
531
		free(buf);
532
533
		return(JS_FALSE);
	}
534

535
	if((script=JS_CompileScript(cx, obj, buf, buflen, NULL, 0))!=NULL) {
deuce's avatar
deuce committed
536
537
538
539
		jsval	rval;

		JS_ExecuteScript(cx, obj, script, &rval);
		JS_SET_RVAL(cx, arglist, rval);
540
	}
541
	free(buf);
542
543
544
545
546
547

	JS_DestroyContext(cx);

    return(JS_TRUE);
}

548
static JSBool
549
js_gc(JSContext *cx, uintN argc, jsval *arglist)
550
{
551
552
	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
	jsval *argv=JS_ARGV(cx, arglist);
553
	JSBool			forced=JS_TRUE;
554
	js_callback_t*	cb;
555

deuce's avatar
deuce committed
556
557
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

558
	if((cb=(js_callback_t*)JS_GetPrivate(cx,obj))==NULL)
559
		return(JS_FALSE);
560
561
562
563
564
565
566
567
568

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

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

569
	cb->gc_attempts++;
570

571
572
573
	return(JS_TRUE);
}

574
static JSBool
575
js_report_error(JSContext *cx, uintN argc, jsval *arglist)
576
{
577
	jsval *argv=JS_ARGV(cx, arglist);
578
	char	*p = NULL;
deuce's avatar
deuce committed
579

580
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
581
	HANDLE_PENDING(cx, p);
582
583
584
585
586
587
	if(p==NULL)
		JS_ReportError(cx,"NULL");
	else {
		JS_ReportError(cx,"%s",p);
		free(p);
	}
588

589
590
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

591
592
593
594
595
	if(argc>1 && argv[1]==JSVAL_TRUE)
		return(JS_FALSE);	/* fatal */

	return(JS_TRUE);
}
596

597
static JSBool
598
js_on_exit(JSContext *cx, uintN argc, jsval *arglist)
599
{
600
	JSObject *scope=JS_GetScopeChain(cx);
601
	JSObject *glob=JS_GetGlobalObject(cx);
602
	jsval *argv=JS_ARGV(cx, arglist);
603
604
	global_private_t*	pd;
	str_list_t	list;
605
	str_list_t	oldlist;
606
	char		*p = NULL;
607

deuce's avatar
deuce committed
608
609
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);

610
	if(glob==scope) {
611
612
613
614
615
616
617
		if((pd=(global_private_t*)JS_GetPrivate(cx,glob))==NULL)
			return(JS_FALSE);
		if(pd->exit_func==NULL)
			pd->exit_func=strListInit();
		list=pd->exit_func;
	}
	else {
618
		list=(str_list_t)JS_GetPrivate(cx,scope);
619
620
		if(list==NULL) {
			list=strListInit();
621
			JS_SetPrivate(cx,scope,list);
622
623
		}
	}
624

625
	JSVALUE_TO_MSTRING(cx, argv[0], p, NULL);
626
	HANDLE_PENDING(cx, p);
627
628
	if(!p)
		return JS_TRUE;
deuce's avatar
deuce committed
629
	oldlist=list;
630
	strListPush(&list,p);
631
	free(p);
deuce's avatar
deuce committed
632
633
634
635
636
637
	if(oldlist != list) {
		if(glob==scope)
			pd->exit_func=list;
		else
			JS_SetPrivate(cx,scope,list);
	}
638
639
640
	return(JS_TRUE);
}

641
static JSBool
642
js_get_parent(JSContext *cx, uintN argc, jsval *arglist)
643
{
644
	jsval *argv=JS_ARGV(cx, arglist);
645
646
647
648
649
650
	JSObject* child=NULL;
	JSObject* parent;

	if(JS_ValueToObject(cx, argv[0], &child)
		&& child!=NULL
		&& (parent=JS_GetParent(cx,child))!=NULL)
651
		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(parent));
652
653
654
655

	return(JS_TRUE);
}

656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
static JSBool js_getsize(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);
	JSObject* tmp_obj;

	if(!JSVAL_IS_OBJECT(argv[0])) {
		JS_ReportError(cx, "get_size() error!  Parameter is not an object.");
		return(JS_FALSE);
	}
	tmp_obj=JSVAL_TO_OBJECT(argv[0]);
	if(!tmp_obj)
		return(JS_FALSE);
	JS_SET_RVAL(cx, arglist, DOUBLE_TO_JSVAL(JS_GetObjectTotalSize(cx, tmp_obj)));
	return(JS_TRUE);
}

static JSBool js_flatten(JSContext *cx, uintN argc, jsval *arglist)
{
	jsval	*argv=JS_ARGV(cx, arglist);

	if(!JSVAL_IS_STRING(argv[0])) {
		JS_ReportError(cx, "get_size() error!  Parameter is not a string.");
		return(JS_FALSE);
	}
	JS_FlattenString(cx, JSVAL_TO_STRING(argv[0]));
	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
	return(JS_TRUE);
}

685
static jsSyncMethodSpec js_functions[] = {
rswindell's avatar
rswindell committed
686
	{"eval",            js_eval,            0,	JSTYPE_UNDEF,	JSDOCSTR("script")
687
	,JSDOCSTR("evaluate a JavaScript string in its own (secure) context, returning the result")
688
	,311
689
	},		
rswindell's avatar
rswindell committed
690
	{"gc",				js_gc,				0,	JSTYPE_VOID,	JSDOCSTR("forced=<tt>true</tt>")
691
692
693
	,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")
694
	,311
695
	},
rswindell's avatar
rswindell committed
696
697
	{"on_exit",			js_on_exit,			1,	JSTYPE_VOID,	JSDOCSTR("to_eval")
	,JSDOCSTR("add a string to evaluate/execute (LIFO stack) upon script's termination")
698
	,313
699
	},
rswindell's avatar
rswindell committed
700
	{"report_error",	js_report_error,	1,	JSTYPE_VOID,	JSDOCSTR("error [,fatal=<tt>false</tt>]")
701
702
	,JSDOCSTR("report an error using the standard JavaScript error reporting mechanism "
	"(including script filename and line number), "
703
704
	"if <i>fatal</i> is <i>true</i>, immediately terminates script")
	,313
705
	},
rswindell's avatar
rswindell committed
706
	{"get_parent",		js_get_parent,		1,	JSTYPE_OBJECT,	JSDOCSTR("object")
707
	,JSDOCSTR("return the parent of the specified object")
rswindell's avatar
rswindell committed
708
	,314
709
	},
710
711
712
713
714
	{"get_size",		js_getsize,			1,	JSTYPE_NUMBER,	JSDOCSTR("[object]")
	,JSDOCSTR("return the size in bytes the object uses in memory (forces GC) ")
	,316
	},
	{"flatten_string",	js_flatten,			1,	JSTYPE_VOID,	JSDOCSTR("[string]")
715
	,JSDOCSTR("flatten a string, optimizing allocated memory used for concatenated strings")
716
717
	,316
	},
718
719
720
721
722
723
	{"exec",	js_execfile,			1,	JSTYPE_NUMBER,	JSDOCSTR("filename [, startup_dir], <i>object</i> scope [,...]")
	,JSDOCSTR("execute a script within the specified scope.  The main difference between this method "
	"and <tt>load()</tt> is that scripts called this way can call <tt>exit()</tt> without terminating the caller.  If it does, any "
	"<tt>on_exit()</tt> handlers will be evaluated in scripts scope when the script exists. <br>"
	"NOTE: to get a child of the current scope, you need to create an object in the current scope. "
	"An anonymous object can be created using '<tt>new function(){}</tt>'. <br>"
724
	"NOTE: Use <tt>js.exec.apply()</tt> if you need to pass a variable number of arguments to the executed script.")
725
	,31702
deuce's avatar
deuce committed
726
	},
727
728
729
	{0}
};

730
static JSBool js_internal_resolve(JSContext *cx, JSObject *obj, jsid id)
deuce's avatar
deuce committed
731
732
{
	char*			name=NULL;
733
	JSBool			ret;
deuce's avatar
deuce committed
734

deuce's avatar
deuce committed
735
736
737
738
	if(id != JSID_VOID && id != JSID_EMPTY) {
		jsval idval;
		
		JS_IdToValue(cx, id, &idval);
739
740
		if(JSVAL_IS_STRING(idval)) {
			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
741
			HANDLE_PENDING(cx, name);
742
		}
deuce's avatar
deuce committed
743
	}
deuce's avatar
deuce committed
744

745
746
747
748
	ret=js_SyncResolve(cx, obj, name, js_properties, js_functions, NULL, 0);
	if(name)
		free(name);
	return(ret);
deuce's avatar
deuce committed
749
750
751
752
}

static JSBool js_internal_enumerate(JSContext *cx, JSObject *obj)
{
deuce's avatar
deuce committed
753
	return(js_internal_resolve(cx, obj, JSID_VOID));
deuce's avatar
deuce committed
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
}

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		*/
};

769
void DLLCALL js_EvalOnExit(JSContext *cx, JSObject *obj, js_callback_t* cb)
770
771
772
{
	char*	p;
	jsval	rval;
773
	JSObject* script;
774
	BOOL	auto_terminate=cb->auto_terminate;
775
776
777
778
779
780
781
782
783
784
	JSObject	*glob=JS_GetGlobalObject(cx);
	global_private_t *pt;
	str_list_t	list;

	if(glob==obj) {
		pt=(global_private_t *)JS_GetPrivate(cx,JS_GetGlobalObject(cx));		
		list=pt->exit_func;
	}
	else
		list=JS_GetPrivate(cx,obj);
785

786
	cb->auto_terminate=FALSE;
787

788
	while((p=strListPop(&list))!=NULL) {
789
790
791
		if((script=JS_CompileScript(cx, obj, p, strlen(p), NULL, 0))!=NULL) {
			JS_ExecuteScript(cx, obj, script, &rval);
		}
deuce's avatar
deuce committed
792
		free(p);
793
794
	}

795
	strListFree(&list);
796
797
798
799
	if(glob != obj)
		JS_SetPrivate(cx,obj,NULL);
	else
		pt->exit_func=NULL;
800

801
	if(auto_terminate)
802
		cb->auto_terminate = TRUE;
803
804
}

805
JSObject* DLLCALL js_CreateInternalJsObject(JSContext* cx, JSObject* parent, js_callback_t* cb, js_startup_t* startup)
806
807
808
809
810
811
812
{
	JSObject*	obj;

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

813
	if(!JS_SetPrivate(cx, obj, cb))	/* Store a pointer to js_callback_t */
814
815
		return(NULL);

816
817
818
	if(startup!=NULL) {
		JSObject*	load_path_list;
		jsval		val;
819
		str_list_t	load_path;
820
821
822
823
824
825
826

		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);

827
		if((load_path=strListSplitCopy(NULL, startup->load_path, ",")) != NULL) {
828
829
830
			JSString*	js_str;
			unsigned	i;

831
832
833
			for(i=0; load_path[i]!=NULL; i++) {
				if((js_str=JS_NewStringCopyZ(cx, load_path[i]))==NULL)
					break;
834
835
				val=STRING_TO_JSVAL(js_str);
				if(!JS_SetElement(cx, load_path_list, i, &val))
836
					break;
837
			}
838
			strListFree(&load_path);
839
840
841
		}
	}

842
#ifdef BUILD_JSDOCS
843
	js_DescribeSyncObject(cx,obj,"JavaScript engine internal control object",311);
844
845
846
847
848
	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", prop_desc, JSPROP_READONLY);
#endif

	return(obj);
}
849

850
851
852
853
854
855
856
857
858
859
#if defined(_MSC_VER)
void msvc_invalid_parameter_handler(const wchar_t* expression,
   const wchar_t* function, 
   const wchar_t* file, 
   unsigned int line, 
   uintptr_t pReserved)
{
}
#endif

860
void DLLCALL js_PrepareToExecute(JSContext *cx, JSObject *obj, const char *filename, const char* startup_dir, JSObject *scope)
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
{
	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);
		}
885
886
887
888
889
		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);
890
891
		JS_DefineProperty(cx, js, "scope", OBJECT_TO_JSVAL(scope)
			,NULL,NULL,JSPROP_ENUMERATE|JSPROP_READONLY);
892
	}
893
894
	JS_DefineProperty(cx, scope, "exit_code", JSVAL_NULL
		,NULL,NULL,JSPROP_ENUMERATE|JSPROP_PERMANENT);
895
896
897
#if defined(_MSC_VER)
	_set_invalid_parameter_handler(msvc_invalid_parameter_handler);
#endif
898
}