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 = @CONFIG_ECMASCRIPT@
|
||||||
CONFIG_ECMASCRIPT_SEE = @CONFIG_ECMASCRIPT_SEE@
|
CONFIG_ECMASCRIPT_SEE = @CONFIG_ECMASCRIPT_SEE@
|
||||||
CONFIG_ECMASCRIPT_SMJS = @CONFIG_ECMASCRIPT_SMJS@
|
CONFIG_ECMASCRIPT_SMJS = @CONFIG_ECMASCRIPT_SMJS@
|
||||||
|
CONFIG_ECMASCRIPT_SMJS_HEARTBEAT = @CONFIG_ECMASCRIPT_SMJS@
|
||||||
CONFIG_EXMODE = @CONFIG_EXMODE@
|
CONFIG_EXMODE = @CONFIG_EXMODE@
|
||||||
CONFIG_FASTMEM = @CONFIG_FASTMEM@
|
CONFIG_FASTMEM = @CONFIG_FASTMEM@
|
||||||
CONFIG_FINGER = @CONFIG_FINGER@
|
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(getifaddrs getpwnam inet_pton inet_ntop)
|
||||||
AC_CHECK_FUNCS(fflush fsync fseeko ftello sigaction)
|
AC_CHECK_FUNCS(fflush fsync fseeko ftello sigaction)
|
||||||
AC_CHECK_FUNCS(gettimeofday clock_gettime)
|
AC_CHECK_FUNCS(gettimeofday clock_gettime)
|
||||||
|
AC_CHECK_FUNCS(setitimer, HAVE_SETITIMER=yes)
|
||||||
|
|
||||||
AC_CHECK_FUNCS([cygwin_conv_to_full_win32_path])
|
AC_CHECK_FUNCS([cygwin_conv_to_full_win32_path])
|
||||||
|
|
||||||
@ -654,6 +655,8 @@ AC_MSG_RESULT($cf_result)
|
|||||||
CONFIG_SPIDERMONKEY="$cf_result"
|
CONFIG_SPIDERMONKEY="$cf_result"
|
||||||
if test "$cf_result" = "yes"; then
|
if test "$cf_result" = "yes"; then
|
||||||
AC_CHECK_FUNCS([[JS_ReportAllocationOverflow]])
|
AC_CHECK_FUNCS([[JS_ReportAllocationOverflow]])
|
||||||
|
AC_CHECK_FUNCS(JS_SetBranchCallback)
|
||||||
|
AC_CHECK_FUNCS(JS_TriggerOperationCallback, HAVE_JS_TRIGGEROPERATIONCALLBACK=yes)
|
||||||
fi
|
fi
|
||||||
EL_RESTORE_FLAGS
|
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_SEE)
|
||||||
AC_SUBST(CONFIG_ECMASCRIPT_SMJS)
|
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
|
# Optional Spidermonkey-based ECMAScript browser scripting
|
||||||
|
@ -32,7 +32,11 @@ struct ecmascript_interpreter {
|
|||||||
/* The code evaluated by setTimeout() */
|
/* The code evaluated by setTimeout() */
|
||||||
struct string code;
|
struct string code;
|
||||||
|
|
||||||
|
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
|
||||||
|
struct heartbeat *heartbeat;
|
||||||
|
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
|
||||||
time_t exec_start;
|
time_t exec_start;
|
||||||
|
#endif
|
||||||
|
|
||||||
/* This is a cross-rerenderings accumulator of
|
/* This is a cross-rerenderings accumulator of
|
||||||
* @document.onload_snippets (see its description for juicy details).
|
* @document.onload_snippets (see its description for juicy details).
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
#include "ecmascript/spidermonkey.h"
|
#include "ecmascript/spidermonkey.h"
|
||||||
#include "ecmascript/spidermonkey/document.h"
|
#include "ecmascript/spidermonkey/document.h"
|
||||||
#include "ecmascript/spidermonkey/form.h"
|
#include "ecmascript/spidermonkey/form.h"
|
||||||
|
#include "ecmascript/spidermonkey/heartbeat.h"
|
||||||
#include "ecmascript/spidermonkey/location.h"
|
#include "ecmascript/spidermonkey/location.h"
|
||||||
#include "ecmascript/spidermonkey/navigator.h"
|
#include "ecmascript/spidermonkey/navigator.h"
|
||||||
#include "ecmascript/spidermonkey/unibar.h"
|
#include "ecmascript/spidermonkey/unibar.h"
|
||||||
@ -109,6 +110,7 @@ reported:
|
|||||||
JS_ClearPendingException(ctx);
|
JS_ClearPendingException(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if !defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) && defined(HAVE_JS_SETBRANCHCALLBACK)
|
||||||
static JSBool
|
static JSBool
|
||||||
safeguard(JSContext *ctx, JSScript *script)
|
safeguard(JSContext *ctx, JSScript *script)
|
||||||
{
|
{
|
||||||
@ -133,6 +135,7 @@ setup_safeguard(struct ecmascript_interpreter *interpreter,
|
|||||||
interpreter->exec_start = time(NULL);
|
interpreter->exec_start = time(NULL);
|
||||||
JS_SetBranchCallback(ctx, safeguard);
|
JS_SetBranchCallback(ctx, safeguard);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -173,6 +176,9 @@ spidermonkey_get_interpreter(struct ecmascript_interpreter *interpreter)
|
|||||||
* some kind of bytecode cache. (If we will ever do that.) */
|
* some kind of bytecode cache. (If we will ever do that.) */
|
||||||
JS_SetOptions(ctx, JSOPTION_VAROBJFIX | JSOPTION_COMPILE_N_GO);
|
JS_SetOptions(ctx, JSOPTION_VAROBJFIX | JSOPTION_COMPILE_N_GO);
|
||||||
JS_SetErrorReporter(ctx, error_reporter);
|
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);
|
window_obj = JS_NewObject(ctx, (JSClass *) &window_class, NULL, NULL);
|
||||||
if (!window_obj) goto release_and_fail;
|
if (!window_obj) goto release_and_fail;
|
||||||
@ -264,10 +270,17 @@ spidermonkey_eval(struct ecmascript_interpreter *interpreter,
|
|||||||
assert(interpreter);
|
assert(interpreter);
|
||||||
if (!js_module_init_ok) return;
|
if (!js_module_init_ok) return;
|
||||||
ctx = interpreter->backend_data;
|
ctx = interpreter->backend_data;
|
||||||
|
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
|
||||||
|
interpreter->heartbeat = add_heartbeat(interpreter);
|
||||||
|
#elif defined(HAVE_JS_SETBRANCHCALLBACK)
|
||||||
setup_safeguard(interpreter, ctx);
|
setup_safeguard(interpreter, ctx);
|
||||||
|
#endif
|
||||||
interpreter->ret = ret;
|
interpreter->ret = ret;
|
||||||
JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
|
JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
|
||||||
code->source, code->length, "", 0, &rval);
|
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,
|
spidermonkey_eval_stringback(struct ecmascript_interpreter *interpreter,
|
||||||
struct string *code)
|
struct string *code)
|
||||||
{
|
{
|
||||||
|
JSBool ret;
|
||||||
JSContext *ctx;
|
JSContext *ctx;
|
||||||
jsval rval;
|
jsval rval;
|
||||||
|
|
||||||
assert(interpreter);
|
assert(interpreter);
|
||||||
if (!js_module_init_ok) return NULL;
|
if (!js_module_init_ok) return NULL;
|
||||||
ctx = interpreter->backend_data;
|
ctx = interpreter->backend_data;
|
||||||
setup_safeguard(interpreter, ctx);
|
|
||||||
interpreter->ret = NULL;
|
interpreter->ret = NULL;
|
||||||
if (JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx),
|
#if defined(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT)
|
||||||
code->source, code->length, "", 0, &rval)
|
interpreter->heartbeat = add_heartbeat(interpreter);
|
||||||
== JS_FALSE) {
|
#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;
|
return NULL;
|
||||||
}
|
}
|
||||||
if (JSVAL_IS_VOID(rval)) {
|
if (JSVAL_IS_VOID(rval)) {
|
||||||
@ -309,14 +330,21 @@ spidermonkey_eval_boolback(struct ecmascript_interpreter *interpreter,
|
|||||||
assert(interpreter);
|
assert(interpreter);
|
||||||
if (!js_module_init_ok) return 0;
|
if (!js_module_init_ok) return 0;
|
||||||
ctx = interpreter->backend_data;
|
ctx = interpreter->backend_data;
|
||||||
setup_safeguard(interpreter, ctx);
|
|
||||||
interpreter->ret = NULL;
|
interpreter->ret = NULL;
|
||||||
fun = JS_CompileFunction(ctx, NULL, "", 0, NULL, code->source,
|
fun = JS_CompileFunction(ctx, NULL, "", 0, NULL, code->source,
|
||||||
code->length, "", 0);
|
code->length, "", 0);
|
||||||
if (!fun)
|
if (!fun)
|
||||||
return -1;
|
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);
|
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()" */
|
if (ret == 2) { /* onClick="history.back()" */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ top_builddir=../../..
|
|||||||
include $(top_builddir)/Makefile.config
|
include $(top_builddir)/Makefile.config
|
||||||
INCLUDES += $(SPIDERMONKEY_CFLAGS)
|
INCLUDES += $(SPIDERMONKEY_CFLAGS)
|
||||||
|
|
||||||
|
OBJS-$(CONFIG_ECMASCRIPT_SMJS_HEARTBEAT) += heartbeat.o
|
||||||
|
|
||||||
OBJS = document.o form.o location.o navigator.o unibar.o window.o
|
OBJS = document.o form.o location.o navigator.o unibar.o window.o
|
||||||
|
|
||||||
include $(top_srcdir)/Makefile.lib
|
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