Renamed test.c to js-server.c
authorMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 15 Jul 2005 21:43:45 +0000 (21:43 +0000)
committerMatthew Mondor <mmondor@pulsar-zone.net>
Fri, 15 Jul 2005 21:43:45 +0000 (21:43 +0000)
tests/js-test/src/GNUmakefile
tests/js-test/src/js-server.c [new file with mode: 0644]

index 6bc0f75..7c9779c 100644 (file)
@@ -1,22 +1,22 @@
-# $Id: GNUmakefile,v 1.3 2005/07/14 00:55:06 mmondor Exp $
+# $Id: GNUmakefile,v 1.4 2005/07/15 21:43:45 mmondor Exp $
 
 JS_CFLAGS := $(shell spidermonkey-config -dc)
 JS_LDFLAGS := $(shell spidermonkey-config -dl)
 
 OBJS := $(addprefix classes/,js_fd.o js_errno.o js_file.o)
-OBJS += test.o
+OBJS += js-server.o
 
 CFLAGS += $(JS_CFLAGS) -Iclasses -Wall
 LDFLAGS += $(JS_LDFLAGS)
 
 
-all: test
+all: js-server
 
 %.o: %.c
        cc -c ${CFLAGS} -o $@ $<
 
-test: $(OBJS)
+js-server: $(OBJS)
        cc -o $@ -lc ${LDFLAGS} $(OBJS)
 
 clean:
-       rm -f test $(OBJS)
+       rm -f js-server $(OBJS)
diff --git a/tests/js-test/src/js-server.c b/tests/js-test/src/js-server.c
new file mode 100644 (file)
index 0000000..0c97c31
--- /dev/null
@@ -0,0 +1,260 @@
+/* $Id: js-server.c,v 1.1 2005/07/15 21:43:45 mmondor Exp $ */
+
+/* Copyright (c) 2004, Matthew Mondor */
+
+
+/*
+ * TODO:
+ *
+ * - Verify with Brendan Eich:
+ *   - If reusing the context to execute several other scripts, it is
+ *     important that they not be able to add global properties or methods.
+ *     This seems to currently work using a custom api_class_property_add().
+ *     This however also required standard properties to be shared
+ *     (JS_PROP_SHARED), otherwise api_class_property_add() would be called
+ *     and even setting values to existing API system properties would fail in
+ *     user scripts. Scealing was also too strict.
+ *     I assumed that JS_AddNamedRoot() was required for the API class to
+ *     never be freed, so that it can be reused after a call to
+ *     js_context_reset(). Perhaps this is not necessary.
+ */
+
+
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <jsapi.h>
+
+#include <js_fd.h>
+#include <js_errno.h>
+
+#include <js_file.h>
+
+
+
+/* Size runtime objects must take to run the GC */
+#define GCBYTES    1048576 /* 1MB */
+
+/* Size of stack to allocate for every context */
+#define STACKBYTES 8192    /* 8KB */
+
+
+
+/* Structure used to link a context with custom objects we need to perform
+ * some cleanup from before destroying the context. Ideally managed via
+ * mmpool(3) in a real world application for slap management and recycling.
+ * We'll have one of these per process in our pool of processes.
+ */
+typedef struct {
+       JSRuntime       *rt;
+       JSContext       *ctx;
+       JSObject        *global, *class_fd, *class_errno;
+} js_context_t;
+
+
+
+/* Defaults for the global class */
+static JSClass global_class = {
+       "global", 0, JS_PropertyStub, JS_PropertyStub,
+       JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub,
+       JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub
+};
+
+
+
+/* Only here for a test */
+int            main(int, char **);
+
+static JSBool  branch_callback(JSContext *, JSScript *);
+
+/* Exported API */
+js_context_t   *js_context_init(size_t, size_t);
+void           js_context_destroy(js_context_t *);
+void           js_context_reset(js_context_t *);
+
+
+
+int
+main(int argc, char **argv)
+{
+       file_t          *file;
+       js_context_t    *cctx;
+
+       if (argc != 2) {
+               (void) fprintf(stderr, "Usage: test <scriptfile>\n");
+               exit(EXIT_FAILURE);
+       }
+       if ((file = file_load(argv[1])) == NULL) {
+               (void) fprintf(stderr, "Error loading '%s'\n", argv[1]);
+               exit(EXIT_FAILURE);
+       }
+
+       /*
+        * We always need at least one runtime per process, at least one
+        * context per thread and at least a global object per context
+        * (standard classes, like Date).
+        */
+       if ((cctx = js_context_init(GCBYTES, STACKBYTES)) == NULL) {
+               file_free(file);
+               (void) fprintf(stderr, "js_context_init()\n");
+               exit(EXIT_FAILURE);
+       }
+
+       /*
+        * This is a very useful and important feature, enable our callback
+        * function which will get called whenever the script branches
+        * backwards, returns from a function or exits. It allows us to
+        * even maintain control in cases where the script loops endlessly.
+        */
+       (void) JS_SetBranchCallback(cctx->ctx, branch_callback);
+
+       /*
+        * Now enable addProperty() protection for all classes using our
+        * custom api_class_property_add() function. This will prevent user
+        * code from adding properties or methods to the API class for
+        * instance.
+        * This however requires that properties use the JSPROP_SHARED flag
+        * since addProperty() method would internally get called to create
+        * shadow copies for the runtime otherwise.
+        */
+       /*
+       api_class_protect = JS_TRUE;
+       */
+
+       /*
+        * Now execute script loaded into our file_t.
+        * We simplify this process by calling JS_EvaluateScript() which
+        * will first tokenize/compile the result, and then interpret/run it.
+        * Moreover, it allows the script to optionally return a value
+        * directly like if it was a function.
+        * Alternatively, we could use JS_CompileFile() or JS_CompileScript()
+        * to pre-tokenize the script, and JS_ExecuteScript() to interpret it.
+        */
+       {
+               jsval           rval, pval;
+               JSString        *str;
+               int             i;
+
+               if (JS_EvaluateScript(cctx->ctx, cctx->global, file->data,
+                   file->size, argv[1], 1, &rval)) {
+                       str = JS_ValueToString(cctx->ctx, rval);
+                       (void) printf("Script result: %s\n",
+                           JS_GetStringBytes(str));
+                       /*
+                        * Attempt to call JS function "callMe" if the script
+                        * created it.
+                        */
+                       for (i = 0; i < 10; i++) {
+                               pval = INT_TO_JSVAL(i);
+                               if (!JS_CallFunctionName(cctx->ctx,
+                                   cctx->global, "callMe", 1, &pval, &rval))
+                                       break;
+                       }
+               } else {
+                       /* XXX how to obtain error and stack backtrace? */
+               }
+       }
+
+       /* Cleanup */
+       file_free(file);
+       js_context_destroy(cctx);
+
+       exit(EXIT_SUCCESS);
+}
+
+/*
+ * This function is called during the execution of the script so that we can
+ * remain in control of the application. If we only allow the scripts to
+ * define functions for callbacks, we can use the first instance if this event
+ * to abort the script if wanted, as well. We can then set an alternative
+ * callback function and execute the script provided functions at specific
+ * events. Of course, it also would be possible to use setitimer(2) to have
+ * a SIGALRM signal trigger a function at regular set intervals.
+ */
+/* ARGSUSED */
+static JSBool
+branch_callback(JSContext *ctx, JSScript *script)
+{
+       /*jsval         pval, rval;*/
+       static int      count = 0;
+
+       /* Call callMe() script-provided handler if any, as a test */
+       /*
+       pval = INT_TO_JSVAL(count++);
+       (void) JS_CallFunctionName(ctx, JS_GetGlobalObject(ctx), "callMe", 1,
+           &pval, &rval);
+        */
+
+       if (++count > 1000) {
+               count = 0;
+               JS_MaybeGC(ctx);
+       }
+
+       /* Returning JS_FALSE here aborts the script */
+       return JS_TRUE;
+}
+
+
+/* Exported API */
+
+js_context_t *
+js_context_init(size_t gc_size, size_t stack_size)
+{
+       js_context_t    *cctx;
+
+       if ((cctx = malloc(sizeof(js_context_t))) == NULL ||
+           (cctx->rt = JS_NewRuntime(gc_size)) == NULL ||
+           (cctx->ctx = JS_NewContext(cctx->rt, stack_size)) == NULL ||
+           (cctx->global = JS_NewObject(cctx->ctx, &global_class, NULL,
+               NULL)) == NULL ||
+           !JS_InitStandardClasses(cctx->ctx, cctx->global) ||
+           (cctx->class_fd = js_InitFDClass(cctx->ctx, cctx->global))
+               == NULL ||
+           (cctx->class_errno = js_InitErrnoClass(cctx->ctx, cctx->global))
+               == NULL) {
+               /* An error, free any partially allocated resources */
+               if (cctx != NULL)
+                       js_context_destroy(cctx);
+
+               return NULL;
+       }
+
+       return cctx;
+}
+
+void
+js_context_destroy(js_context_t *cctx)
+{
+
+       assert(cctx != NULL);
+
+       if (cctx->ctx != NULL)
+               JS_DestroyContext(cctx->ctx);
+       if (cctx->rt != NULL)
+               JS_DestroyRuntime(cctx->rt);
+
+       free(cctx);
+}
+
+/*
+ * This function should permit to restore the context to a consistent, known
+ * state before a new script can be executed using the same context instead of
+ * having to destroy and recreate contexts everytime.
+ */
+/* ARGSUSED */
+void
+js_context_reset(js_context_t *cctx)
+{
+
+       /* XXX */
+       /* NOOP */
+}