mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05: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:
parent
1208a8dd2a
commit
f31cf6f9fe
@ -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@
|
||||
|
12
configure.in
12
configure.in
@ -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
|
||||
|
@ -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).
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
125
src/ecmascript/spidermonkey/heartbeat.c
Normal file
125
src/ecmascript/spidermonkey/heartbeat.c
Normal 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);
|
||||
}
|
24
src/ecmascript/spidermonkey/heartbeat.h
Normal file
24
src/ecmascript/spidermonkey/heartbeat.h
Normal 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
|
Loading…
Reference in New Issue
Block a user