-/* $Id: js_gcroot.c,v 1.2 2006/10/05 18:43:30 mmondor Exp $ */
+/* $Id: js_gcroot.c,v 1.3 2006/10/18 05:07:42 mmondor Exp $ */
/*
* Copyright (c) 2006, Matthew Mondor
* ALL RIGHTS RESERVED.
*/
+/*
+ * This module provides APIs to several tricks which may be required to
+ * support the functionality of a few C libraries when writing their JS stubs.
+ * Here are cases where it can be useful:
+ *
+ * - An object must be provided to a C library for an arbitrary amount of time
+ * and we must ensure that it does not get freed by the potential
+ * auto-destruction of the object by the garbage collector.
+ * An example of this is in js_pgsql.c where PQtrace() can be provided a
+ * FILE *. We thus can store the wrapping File object as a property into
+ * the context-global rooted GCRoot object, and delete it from the object
+ * when PQuntrace() is called.
+ * An application using this functionality must call js_InitGCRoot() when
+ * creating contexts and js_DestroyGCRoot() before destroying the contexts.
+ * It may then call js_GCRoot() to obtain the JSObject pointer of the rooted
+ * object to assiciate properties with. Since this clobbers the private
+ * data which can be set for a context using JS_SetContextPrivate(),
+ * alternate functions are provided to store arbitrary context-specific
+ * data: js_GCRoot_udata_set()/js_GCRoot_udata_get().
+ * - A C API function must be provided an object which is internally wrapped
+ * by a JSObject, but to which it is impossibe to pass the JSContext * and
+ * JSObject *. There must then be a way to map internal arbitrary C
+ * pointers to their corresponding JSContext/JSObject pointers, in which
+ * case we may use the js_map_add() function at object construction and
+ * js_map_remove() at its destruction, and js_map_lookup() to obtain the
+ * necessary information from within the C function. This case most often
+ * occurs for callback functions, like for the notice receiver callback
+ * in js_pgsql.c.
+ * To use this functionality, the application must call js_map_init() after
+ * creating the runtime, and js_map_destroy() before destroying the runtime.
+ * Provision is also made to store an additional user data pointer.
+ * - In cases where the private data set on an object also must hold the
+ * JSContext * and JSObject * associated with it, it is possible to use
+ * the js_udata_alloc() and js_udata_free() to create/destroy private
+ * data objects. Additionally, udata_t also allows to store more than one
+ * void *, which may be useful.
+ */
+
#include <sys/types.h>
*/
typedef struct cxspec {
JSObject *gcroot;
- void *udata;
+ udata_t *udata;
} cxspec_t;
/*
+ * Static functions prototypes
+ */
+static int omap_keycmp(const void *, const void *, size_t);
+static u_int32_t omap_keyhash(const void *, size_t);
+
+
+
+/*
* Static globals
*/
JS_FinalizeStub
};
+/*
+ * XXX since these are runtime-global, they should be lock-protected if we
+ * wanted thread-safe code.
+ */
+static pool_t udata_pool;
+static pool_t omap_pool;
+static hashtable_t omap_table;
+
/*
cxspec_t *d;
if ((d = JS_GetContextPrivate(cx)) != NULL) {
+ assert(d->udata == NULL);
if (d->gcroot != NULL) {
if (!JS_RemoveRoot(cx, &d->gcroot))
(void) fprintf(stderr,
}
void
-js_GCRoot_udata_set(JSContext *cx, void *udata)
+js_GCRoot_udata_set(JSContext *cx, udata_t *udata)
{
cxspec_t *d;
d->udata = udata;
}
-void *
-js_GCroot_udata_get(JSContext *cx)
+udata_t *
+js_GCRoot_udata_get(JSContext *cx)
{
cxspec_t *d;
return d->udata;
}
+
+udata_t *
+js_udata_alloc(JSContext *cx, JSObject *obj)
+{
+ udata_t *udata;
+
+ if ((udata = (udata_t *)pool_alloc(&udata_pool, FALSE)) != NULL) {
+ int i;
+
+ udata->cx = cx;
+ udata->obj = obj;
+ for (i = 0; i < UDATA_MAX; )
+ udata->udata[i++] = NULL;
+ }
+
+ return udata;
+}
+
+void
+js_udata_free(udata_t *udata)
+{
+
+ (void) pool_free((pnode_t *)udata);
+}
+
+
+JSBool
+js_map_init(void)
+{
+
+ if (!pool_init(&udata_pool, "udata_pool", malloc, free, NULL, NULL,
+ sizeof(udata_t), 64, 1, 0))
+ goto err;
+
+ if (!pool_init(&omap_pool, "omap_pool", malloc, free, NULL, NULL,
+ sizeof(omap_t), 64, 1, 0))
+ goto err;
+
+ if (!hashtable_init(&omap_table, "omap_table", HT_DEFAULT_CAPACITY,
+ HT_DEFAULT_FACTOR, malloc, free, omap_keycmp, omap_keyhash, TRUE))
+ goto err;
+
+ return JS_TRUE;
+
+err:
+ if (HASHTABLE_VALID(&omap_table))
+ hashtable_destroy(&omap_table, FALSE);
+ if (POOL_VALID(&omap_pool))
+ (void) pool_destroy(&omap_pool);
+ if (POOL_VALID(&udata_pool))
+ (void) pool_destroy(&udata_pool);
+
+ return JS_FALSE;
+}
+
+void
+js_map_destroy(void)
+{
+
+ if (HASHTABLE_VALID(&omap_table))
+ hashtable_destroy(&omap_table, FALSE);
+ if (POOL_VALID(&omap_pool))
+ (void) pool_destroy(&omap_pool);
+ if (POOL_VALID(&udata_pool))
+ (void) pool_destroy(&udata_pool);
+}
+
+JSBool
+js_map_add(void *o, JSContext *cx, JSObject *obj, void *udata)
+{
+ omap_t *omap;
+
+ assert(cx != NULL && obj != NULL && o != NULL);
+ if ((omap = (omap_t *)pool_alloc(&omap_pool, FALSE)) == NULL)
+ return FALSE;
+
+ omap->mem = o;
+ omap->cx = cx;
+ omap->obj = obj;
+ omap->udata = udata;
+
+ if (!hashtable_link(&omap_table, &omap->node, &omap->mem,
+ sizeof(void *), TRUE)) {
+ (void) pool_free((pnode_t *)omap);
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
+}
+
+omap_t *
+js_map_lookup(void *o)
+{
+
+ return (omap_t *)hashtable_lookup(&omap_table, &o, sizeof(void *));
+}
+
+void
+js_map_remove(omap_t *omap)
+{
+
+ hashtable_unlink(&omap_table, &omap->node);
+ (void) pool_free((pnode_t *)omap);
+}
+
+static int
+omap_keycmp(const void *a, const void *b, size_t s)
+{
+ long da = (long)*(long *)a;
+ long db = (long)*(long *)b;
+
+ return (int)(da - db);
+}
+
+static u_int32_t
+omap_keyhash(const void *a, size_t s)
+{
+ long d = (long)*(long *)a;
+
+ return (u_int32_t)(d & 0xffffffff);
+}
-/* $Id: js_pgsql.c,v 1.9 2006/10/05 18:43:30 mmondor Exp $ */
+/* $Id: js_pgsql.c,v 1.10 2006/10/18 05:07:42 mmondor Exp $ */
/*
* Copyright (c) 2006, Matthew Mondor
* generated objects instances's private data (using structures as necessary
* instead of simply wrapping around the native object's pointer
* (actually PGconn object).
- * - 28.10. Notice Processing XXX
- * Either place one(s) that use syslog(3) transparently, or somehow allow
- * the user to set a custom handler
* - Large objects API
* - Compare to PHP library to verify if missing any nice functions ideas
* - See what to do about the following functions:
jsval *);
static JSBool pgconn_m_PQgetCopyData(JSContext *, JSObject *, uintN, jsval *,
jsval *);
+static JSBool pgconn_m_PQsetNoticeReceiver(JSContext *, JSObject *, uintN,
+ jsval *, jsval *);
+static void notice_receiver(void *, const PGresult *);
+static JSBool pgconn_m_PQsetNoticeProcessor(JSContext *, JSObject *, uintN,
+ jsval *, jsval *);
+static void notice_processor(void *, const char *);
static JSObject *js_InitPGresultClass(JSContext *, JSObject *);
static JSBool pgresult_constructor(JSContext *, JSObject *, uintN, jsval *,
{ "putCopyData", pgconn_m_PQputCopyData, 1, 0, 0 },
{ "putCopyEnd", pgconn_m_PQputCopyEnd, 1, 0, 0 },
{ "getCopyData", pgconn_m_PQgetCopyData, 1, 0, 0 },
+ { "setNoticeReceiver", pgconn_m_PQsetNoticeReceiver, 2, 0, 0 },
+ { "setNoticeProcessor", pgconn_m_PQsetNoticeProcessor, 2, 0, 0 },
{ NULL, NULL, 0, 0, 0 }
};
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Add reverse resolve entry */
+ if (!js_map_add(pgc, cx, o, NULL)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
return JS_TRUE;
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Reverse resolve entry */
+ if (!js_map_add(pgc, cx, o, NULL)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
return JS_TRUE;
if ((pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL))
!= NULL) {
+ omap_t *omap;
+
PQfinish(pgc);
(void) JS_SetPrivate(cx, obj, NULL);
+ if ((omap = js_map_lookup(pgc)) != NULL)
+ js_map_remove(omap);
}
}
if ((pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL))
!= NULL) {
+ omap_t *omap;
+
PQfinish(pgc);
(void) JS_SetPrivate(cx, obj, NULL);
+ if ((omap = js_map_lookup(pgc)))
+ js_map_remove(omap);
}
*rval = OBJECT_TO_JSVAL(NULL);
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Record reverse resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
return JS_TRUE;
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Reverse resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
} else {
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Reverse resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
} else {
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Reverse resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
} else {
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Revere resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
return JS_TRUE;
QUEUE_EXCEPTION("Internal error!");
goto err;
}
+ /* Reverse resolve entry */
+ if (!js_map_add(pgr, cx, o, obj)) {
+ QUEUE_EXCEPTION("js_map_add()");
+ goto err;
+ }
return JS_TRUE;
return JS_FALSE;
}
+static JSBool
+pgconn_m_PQsetNoticeReceiver(JSContext *cx, JSObject *obj, uintN argc,
+ jsval *argv, jsval *rval)
+{
+ PGconn *pgc;
+
+ /*
+ * Verify if our connection object has the np_function property set,
+ * in which case we set it as the return code. If it doesn't have
+ * any, return null.
+ */
+ if (!JS_GetProperty(cx, obj, "np_function", &rval[0]))
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_OBJECT(argv[0]) ||
+ !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[0]))) {
+ QUEUE_EXCEPTION("Argument 1 not a Function Object");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_OBJECT(argv[1]) && !JSVAL_IS_NULL(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not an Object or null");
+ return JS_FALSE;
+ }
+
+ /*
+ * Set the np_function and np_udata properties. This simplifies
+ * things while ensuring that the ojects live as long as the
+ * connection object does. We don't make them enumerable.
+ */
+ if (!JS_DefineProperty(cx, obj, "np_function", argv[0], NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+ if (!JS_DefineProperty(cx, obj, "np_udata", argv[1], NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+
+ /*
+ * Now set our custom notice_processor function. We need to pass both
+ * cx and obj to it, although we can only pass a single pointer. And
+ * we can't set obj as cx specific data, since the data must be
+ * connection specific. Moreover, we can't store private data on the
+ * supplied function object, since the JSFunction * is set already.
+ * Moreover, JS_GetPrivate()/JS_GetInstancePrivate() both need the
+ * JSObject * AND the JSContext *! This means that I need
+ * connection-specific udata other than PGconn *, since it would not
+ * be possible to trace back the allocated data at object
+ * finalization if we just allocated memory and provided the pointer
+ * as udata. Unless I stored the pointer as a property in the
+ * connection object, heh.
+ */
+ pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL);
+ assert(pgc != NULL);
+
+ (void) PQsetNoticeReceiver(pgc, notice_receiver, NULL);
+
+ return JS_TRUE;
+}
+
+static void
+notice_receiver(void *udata, const PGresult *pgr)
+{
+ omap_t *omap;
+ jsval args[2], ret, pgrv, np_function, np_udata;
+
+ omap = js_map_lookup((void *)pgr);
+ assert(omap != NULL);
+
+ /*
+ * JSContext *cx;
+ * JSObject *obj; PGResult
+ * void *udata; PGConn JSObject *
+ */
+
+ pgrv = OBJECT_TO_JSVAL(omap->obj);
+
+ /* We call the user supplied function with the arguments:
+ * udata, PGResult object.
+ * We root the PGResult object to the PGConn meanwhile.
+ * We get out the udata and function to call from the connection
+ * object.
+ */
+ if (!JS_DefineProperty(omap->cx, omap->udata, "np_pgresult", pgrv,
+ NULL, NULL, JSPROP_READONLY | JSPROP_PERMANENT))
+ return;
+
+ if (!JS_GetProperty(omap->cx, omap->udata, "np_function",
+ &np_function) || !JS_GetProperty(omap->cx, omap->udata,
+ "np_udata", &np_udata))
+ goto err;
+
+ args[0] = np_udata;
+ args[1] = pgrv;
+
+ (void) JS_CallFunctionValue(omap->cx, omap->udata, np_function, 2,
+ args, &ret);
+
+err:
+ (void) JS_DeleteProperty(omap->cx, omap->udata, "np_pgresult");
+}
+
+static JSBool
+pgconn_m_PQsetNoticeProcessor(JSContext *cx, JSObject *obj, uintN argc,
+ jsval *argv, jsval *rval)
+{
+ PGconn *pgc;
+
+ /*
+ * Verify if our connection object has the np_function property set,
+ * in which case we set it as the return code. If it doesn't have
+ * any, return null.
+ */
+ if (!JS_GetProperty(cx, obj, "np_function", &rval[0]))
+ *rval = OBJECT_TO_JSVAL(NULL);
+
+ if (argc != 2) {
+ QUEUE_EXCEPTION("Wrong number of arguments");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_OBJECT(argv[0]) ||
+ !JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(argv[0]))) {
+ QUEUE_EXCEPTION("Argument 1 not a Function Object");
+ return JS_FALSE;
+ }
+ if (!JSVAL_IS_OBJECT(argv[1]) && !JSVAL_IS_NULL(argv[1])) {
+ QUEUE_EXCEPTION("Argument 2 not an Object or null");
+ return JS_FALSE;
+ }
+
+ /*
+ * Set the np_function and np_udata properties. This simplifies
+ * things while ensuring that the ojects live as long as the
+ * connection object does. We don't make them enumerable.
+ */
+ if (!JS_DefineProperty(cx, obj, "np_function", argv[0], NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+ if (!JS_DefineProperty(cx, obj, "np_udata", argv[1], NULL, NULL,
+ JSPROP_READONLY | JSPROP_PERMANENT)) {
+ QUEUE_EXCEPTION("Internal error!");
+ return JS_FALSE;
+ }
+
+ /*
+ * Now set our custom notice_processor function. We need to pass both
+ * cx and obj to it, although we can only pass a single pointer. And
+ * we can't set obj as cx specific data, since the data must be
+ * connection specific. Moreover, we can't store private data on the
+ * supplied function object, since the JSFunction * is set already.
+ * Moreover, JS_GetPrivate()/JS_GetInstancePrivate() both need the
+ * JSObject * AND the JSContext *! This means that I need
+ * connection-specific udata other than PGconn *, since it would not
+ * be possible to trace back the allocated data at object
+ * finalization if we just allocated memory and provided the pointer
+ * as udata. Unless I stored the pointer as a property in the
+ * connection object, heh.
+ */
+ pgc = JS_GetInstancePrivate(cx, obj, &pgconn_class, NULL);
+ assert(pgc != NULL);
+
+ (void) PQsetNoticeProcessor(pgc, notice_processor, pgc);
+
+ return JS_TRUE;
+}
+
+/* Internal C notive processor handler which delegates to JS */
+static void
+notice_processor(void *udata, const char *msg)
+{
+ omap_t *omap;
+ PGconn *pgc;
+ jsval args[2], ret, strv, np_function, np_udata;
+ JSString *str;
+
+ /*
+ * The pgc pointer is passed through udata. Lookup by pgc to obtain
+ * the corresponding PGConn JSObject * and its JSContext *.
+ */
+ pgc = udata;
+ omap = js_map_lookup(pgc);
+ assert(omap != NULL);
+
+ /*
+ * JSContext *cx;
+ * JSObject *obj; PGConn
+ * void *udata;
+ */
+
+ if ((str = JS_NewStringCopyZ(omap->cx, msg)) == NULL)
+ return;
+ strv = STRING_TO_JSVAL(str);
+
+ /*
+ * Obtain np_function and np_udata from the connection object we were
+ * supplied with, and all the function with parameters:
+ * udata, String.
+ * Temporarily root the JSString meanwhile.
+ */
+ if (!JS_DefineProperty(omap->cx, omap->obj, "np_message", strv,
+ NULL, NULL, JSPROP_READONLY | JSPROP_PERMANENT))
+ return;
+
+ if (!JS_GetProperty(omap->cx, omap->udata, "np_function",
+ &np_function) || !JS_GetProperty(omap->cx, omap->udata,
+ "np_udata", &np_udata))
+ goto err;
+
+ args[0] = np_udata;
+ args[1] = strv;
+
+ (void) JS_CallFunctionValue(omap->cx, omap->obj, np_function, 2,
+ args, &ret);
+
+err:
+ (void) JS_DeleteProperty(omap->cx, omap->obj, "np_message");
+}
+
/*
* PGresult object control
if ((pgr = JS_GetInstancePrivate(cx, obj, &pgresult_class, NULL))
!= NULL) {
+ omap_t *omap;
+
PQclear(pgr);
(void) JS_SetPrivate(cx, obj, NULL);
+ if ((omap = js_map_lookup(pgr)) != NULL)
+ js_map_remove(omap);
}
}
if ((pgr = JS_GetInstancePrivate(cx, obj, &pgresult_class, NULL))
!= NULL) {
+ omap_t *omap;
+
PQclear(pgr);
(void) JS_SetPrivate(cx, obj, NULL);
+ if ((omap = js_map_lookup(pgr)) != NULL)
+ js_map_remove(omap);
}
*rval = OBJECT_TO_JSVAL(NULL);