1
0
mirror of https://github.com/rkd77/elinks.git synced 2024-06-15 23:35:34 +00:00

Heartbeat code using JS_TriggerOperationCallback

Implement new heartbeat code to catch runaway execution of document
ECMAScript code.  The old code uses JS_SetBranchCallback which is
deprecated in new versions of SpiderMonkey.  The new code uses setitimer(2)
and the JS_SetOperationCallback and JS_TriggerOperationCallback interfaces,
introduced in SpiderMonkey 1.8.1.  Compatibility with both the old
JS_SetBranchCallback and the new interfaces is maintained.
This commit is contained in:
Miciah Dashiel Butler Masters 2009-07-18 23:41:01 +00:00
parent 1208a8dd2a
commit f31cf6f9fe
7 changed files with 201 additions and 5 deletions

View File

@ -117,6 +117,7 @@ CONFIG_DOM = @CONFIG_DOM@
CONFIG_ECMASCRIPT = @CONFIG_ECMASCRIPT@
CONFIG_ECMASCRIPT_SEE = @CONFIG_ECMASCRIPT_SEE@
CONFIG_ECMASCRIPT_SMJS = @CONFIG_ECMASCRIPT_SMJS@
CONFIG_ECMASCRIPT_SMJS_HEARTBEAT = @CONFIG_ECMASCRIPT_SMJS@
CONFIG_EXMODE = @CONFIG_EXMODE@
CONFIG_FASTMEM = @CONFIG_FASTMEM@
CONFIG_FINGER = @CONFIG_FINGER@

View File

@ -283,6 +283,7 @@ AC_CHECK_FUNCS(snprintf vsnprintf asprintf vasprintf)
AC_CHECK_FUNCS(getifaddrs getpwnam inet_pton inet_ntop)
AC_CHECK_FUNCS(fflush fsync fseeko ftello sigaction)
AC_CHECK_FUNCS(gettimeofday clock_gettime)
AC_CHECK_FUNCS(setitimer, HAVE_SETITIMER=yes)
AC_CHECK_FUNCS([cygwin_conv_to_full_win32_path])
@ -654,6 +655,8 @@ AC_MSG_RESULT($cf_result)
CONFIG_SPIDERMONKEY="$cf_result"
if test "$cf_result" = "yes"; then
AC_CHECK_FUNCS([[JS_ReportAllocationOverflow]])
AC_CHECK_FUNCS(JS_SetBranchCallback)
AC_CHECK_FUNCS(JS_TriggerOperationCallback, HAVE_JS_TRIGGEROPERATIONCALLBACK=yes)
fi
EL_RESTORE_FLAGS
@ -668,6 +671,15 @@ EL_CONFIG_DEPENDS(CONFIG_ECMASCRIPT, [CONFIG_ECMASCRIPT_SEE CONFIG_ECMASCRIPT_SM
AC_SUBST(CONFIG_ECMASCRIPT_SEE)
AC_SUBST(CONFIG_ECMASCRIPT_SMJS)
if test "x$CONFIG_ECMASCRIPT_SMJS" = xyes &&
test "x$HAVE_JS_TRIGGEROPERATIONCALLBACK" = xyes &&
test "x$HAVE_SETITIMER" = xyes; then
EL_CONFIG(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT, [ECMAScript heartbeat support])
else
CONFIG_ECMASCRIPT_SMJS_HEARTBEAT=no
fi
AC_SUBST(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
# ===================================================================
# Optional Spidermonkey-based ECMAScript browser scripting

View File

@ -32,7 +32,11 @@ struct ecmascript_interpreter {
/* The code evaluated by setTimeout() */
struct string code;
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
struct heartbeat *heartbeat;
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
time_t exec_start;
#endif
/* This is a cross-rerenderings accumulator of
* @document.onload_snippets (see its description for juicy details).

View File

@ -25,6 +25,7 @@
#include "ecmascript/spidermonkey.h"
#include "ecmascript/spidermonkey/document.h"
#include "ecmascript/spidermonkey/form.h"
#include "ecmascript/spidermonkey/heartbeat.h"
#include "ecmascript/spidermonkey/location.h"
#include "ecmascript/spidermonkey/navigator.h"
#include "ecmascript/spidermonkey/unibar.h"
@ -109,6 +110,7 @@ reported:
JS_ClearPendingException(ctx);
}
#if !defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) && defined(HAVE_JS_SETBRANCHCALLBACK)
static JSBool
safeguard(JSContext *ctx, JSScript *script)
{
@ -133,6 +135,7 @@ setup_safeguard(struct ecmascript_interpreter *interpreter,
interpreter->exec_start = time(NULL);
JS_SetBranchCallback(ctx, safeguard);
}
#endif
static void
@ -173,6 +176,9 @@ spidermonkey_get_interpreter(struct ecmascript_interpreter *interpreter)
* some kind of bytecode cache. (If we will ever do that.) */
JS_SetOptions(ctx, JSOPTION_VAROBJFIX | JSOPTION_COMPILE_N_GO);
JS_SetErrorReporter(ctx, error_reporter);
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
JS_SetOperationCallback(ctx, heartbeat_callback);
#endif
window_obj = JS_NewObject(ctx, (JSClass *) &window_class, NULL, NULL);
if (!window_obj) goto release_and_fail;
@ -264,10 +270,17 @@ spidermonkey_eval(struct ecmascript_interpreter *interpreter,
assert(interpreter);
if (!js_module_init_ok) return;
ctx = interpreter->backend_data;
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
interpreter->heartbeat = add_heartbeat(interpreter);
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
setup_safeguard(interpreter, ctx);
#endif
interpreter->ret = ret;
JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
code->source, code->length, "", 0, &rval);
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
done_heartbeat(interpreter->heartbeat);
#endif
}
@ -275,17 +288,25 @@ unsigned char *
spidermonkey_eval_stringback(struct ecmascript_interpreter *interpreter,
struct string *code)
{
JSBool ret;
JSContext *ctx;
jsval rval;
assert(interpreter);
if (!js_module_init_ok) return NULL;
ctx = interpreter->backend_data;
setup_safeguard(interpreter, ctx);
interpreter->ret = NULL;
if (JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
code->source, code->length, "", 0, &rval)
== JS_FALSE) {
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
interpreter->heartbeat = add_heartbeat(interpreter);
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
setup_safeguard(interpreter, ctx);
#endif
ret = JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
code->source, code->length, "", 0, &rval);
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
done_heartbeat(interpreter->heartbeat);
#endif
if (ret == JS_FALSE) {
return NULL;
}
if (JSVAL_IS_VOID(rval)) {
@ -309,14 +330,21 @@ spidermonkey_eval_boolback(struct ecmascript_interpreter *interpreter,
assert(interpreter);
if (!js_module_init_ok) return 0;
ctx = interpreter->backend_data;
setup_safeguard(interpreter, ctx);
interpreter->ret = NULL;
fun = JS_CompileFunction(ctx, NULL, "", 0, NULL, code->source,
code->length, "", 0);
if (!fun)
return -1;
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
interpreter->heartbeat = add_heartbeat(interpreter);
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
setup_safeguard(interpreter, ctx);
#endif
ret = JS_CallFunction(ctx, NULL, fun, 0, NULL, &rval);
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
done_heartbeat(interpreter->heartbeat);
#endif
if (ret == 2) { /* onClick="history.back()" */
return 0;
}

View File

@ -2,6 +2,8 @@ top_builddir=../../..
include $(top_builddir)/Makefile.config
INCLUDES += $(SPIDERMONKEY_CFLAGS)
OBJS-$(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) += heartbeat.o
OBJS = document.o form.o location.o navigator.o unibar.o window.o
include $(top_srcdir)/Makefile.lib

View File

@ -0,0 +1,125 @@
/* The SpiderMonkey ECMAScript backend heartbeat fuctionality. */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/time.h> /* setitimer(2) */
#include "elinks.h"
#include "ecmascript/spidermonkey/util.h"
#include "config/options.h"
#include "document/view.h"
#include "ecmascript/ecmascript.h"
#include "ecmascript/spidermonkey.h"
#include "ecmascript/spidermonkey/heartbeat.h"
#include "osdep/signals.h"
#include "session/session.h"
#include "util/lists.h"
#include "util/math.h" /* int_upper_bound */
#include "util/memory.h"
#include "viewer/text/vs.h"
static INIT_LIST_OF(struct heartbeat, heartbeats);
static struct itimerval heartbeat_timer = { { 1, 0 }, { 1, 0 } };
/* This callback is installed by JS_SetOperationCallback and triggered
* by JS_TriggerOperationCallback in the heartbeat code below. Returning
* JS_FALSE terminates script execution immediately. */
JSBool
heartbeat_callback(JSContext *ctx)
{
return JS_FALSE;
}
/* Callback for SIGVTALRM. Go through all heartbeats, decrease each
* one's TTL, and call JS_TriggerOperationCallback if a heartbeat's TTL
* goes to 0. */
static void
check_heartbeats(void *data)
{
struct heartbeat *hb;
foreach (hb, heartbeats) {
assert(hb->interpreter);
--hb->ttl;
if (hb->ttl <= 0) {
if (hb->interpreter->vs
&& hb->interpreter->vs->doc_view
&& hb->interpreter->vs->doc_view->session
&& hb->interpreter->vs->doc_view->session->tab
&& hb->interpreter->vs->doc_view->session->tab->term) {
struct session *ses = hb->interpreter->vs->doc_view->session;
struct terminal *term = ses->tab->term;
int max_exec_time = get_opt_int("ecmascript.max_exec_time", ses);
ecmascript_timeout_dialog(term, max_exec_time);
}
JS_TriggerOperationCallback(hb->interpreter->backend_data);
}
}
install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);
}
/* Create a new heartbeat for the given interpreter. */
struct heartbeat *
add_heartbeat(struct ecmascript_interpreter *interpreter)
{
struct session *ses;
struct heartbeat *hb;
assert(interpreter);
if (!interpreter->vs || !interpreter->vs->doc_view)
ses = NULL;
else
ses = interpreter->vs->doc_view->session;
hb = mem_alloc(sizeof(struct heartbeat));
if (!hb) return NULL;
hb->ttl = get_opt_int("ecmascript.max_exec_time", ses);
hb->interpreter = interpreter;
add_to_list(heartbeats, hb);
/* Update the heartbeat timer. */
if (list_is_singleton(*hb)) {
heartbeat_timer.it_value.tv_sec = 1;
setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
}
/* We install the handler every call to add_heartbeat instead of only on
* module initialisation because other code may set other handlers for
* the signal. */
install_signal_handler(SIGVTALRM, check_heartbeats, NULL, 1);
return hb;
}
/* Destroy the given heartbeat. */
void
done_heartbeat(struct heartbeat *hb)
{
assert(hb->interpreter);
/* Stop the heartbeat timer if this heartbeat is the only one. */
if (list_is_singleton(*hb)) {
heartbeat_timer.it_value.tv_sec = 0;
setitimer(ITIMER_VIRTUAL, &heartbeat_timer, NULL);
}
del_from_list(hb);
hb->interpreter->heartbeat = NULL;
mem_free(hb);
}

View File

@ -0,0 +1,24 @@
#ifndef EL__ECMASCRIPT_SPIDERMONKEY_HEARTBEAT_H
#define EL__ECMASCRIPT_SPIDERMONKEY_HEARTBEAT_H
#include "ecmascript/spidermonkey/util.h"
#include "ecmascript/spidermonkey.h"
struct heartbeat {
LIST_HEAD(struct heartbeat);
int ttl; /* Time to live. This value is assigned when the
* script begins execution and is decremented every
* second. When it reaches 0, script execution is
* terminated. */
struct ecmascript_interpreter *interpreter;
};
struct heartbeat *add_heartbeat(struct ecmascript_interpreter *interpreter);
void done_heartbeat(struct heartbeat *hb);
JSBool heartbeat_callback(JSContext *ctx);
#endif