Dear Kalle, > Neil Stewart <neil.stew...@warwick.ac.uk> writes: > > > I have compiled elinks with spidermoney enabled for javascript support. I am > > running a bash script which loads pages useing the -remote > > "openURL(${URL})". After 128 pages, elinks crashes. > > I can reproduce similar crashes with upstream ELinks 0.11.4, > 0.11.5, and 0.12pre4, using libmozjs1d 1.9.0.8-1. It often takes > more than 128 pages, however. > > The problem seems to be that both JS_InitStandardClasses and > JS_InitClass called from spidermonkey_get_interpreter run out of > memory (tested with a breakpoint in JS_ReportOutOfMemory), and > JS_InitClass returns NULL, which spidermonkey_get_interpreter > happily saves as document_obj and gives back to JS_InitClass, > which eventually crashes in one of these calls. > > The out-of-memory error may be caused by ELinks constructing a > large number of JSContexts as described in ELinks bug 981: > http4://bugzilla.elinks.cz/show_bug.cgi?id=981 > > Regardless of whether that one is fixed, I think we need to make > spidermonkey_get_interpreter check the return values.
Thank you for your help with this. I've edited src/ecmascript/spidermonkey.c (attached) in elinks-0.11.4 to prevent calls to JS_* functions with NULL pointers. For example, JS_InitStandardClasses(ctx, window_obj); becomes if (window_obj != NULL ) { JS_InitStandardClasses(ctx, window_obj); } Obviously this is a bodge, but it does allow the pages to display. Presumably the JavaScript would fail to run properly. Anyway, it does now mean crashes about once every 1,000 pages, which was good enough for me to get the job done. Someone who knows what they are doing (i.e., not me) might be able to use this information to fix this properly. Alternatively, someone could implement my approach everywhere and include warning comments in the code and release notes. Thanks for your help. Best, Neil.
/* The SpiderMonkey ECMAScript backend. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include "elinks.h" #include "ecmascript/spidermonkey/util.h" #include "bfu/dialog.h" #include "cache/cache.h" #include "cookies/cookies.h" #include "dialogs/menu.h" #include "dialogs/status.h" #include "document/html/frames.h" #include "document/document.h" #include "document/forms.h" #include "document/view.h" #include "ecmascript/ecmascript.h" #include "ecmascript/spidermonkey.h" #include "ecmascript/spidermonkey/document.h" #include "ecmascript/spidermonkey/form.h" #include "ecmascript/spidermonkey/location.h" #include "ecmascript/spidermonkey/navigator.h" #include "ecmascript/spidermonkey/unibar.h" #include "ecmascript/spidermonkey/window.h" #include "intl/gettext/libintl.h" #include "main/select.h" #include "osdep/newwin.h" #include "osdep/sysname.h" #include "protocol/http/http.h" #include "protocol/uri.h" #include "session/history.h" #include "session/location.h" #include "session/session.h" #include "session/task.h" #include "terminal/tab.h" #include "terminal/terminal.h" #include "util/conv.h" #include "util/string.h" #include "viewer/text/draw.h" #include "viewer/text/form.h" #include "viewer/text/link.h" #include "viewer/text/vs.h" /*** Global methods */ /* TODO? Are there any which need to be implemented? */ /*** The ELinks interface */ static JSRuntime *jsrt; static void error_reporter(JSContext *ctx, const char *message, JSErrorReport *report) { struct ecmascript_interpreter *interpreter = JS_GetContextPrivate(ctx); struct terminal *term; unsigned char *strict, *exception, *warning, *error; struct string msg; assert(interpreter && interpreter->vs && interpreter->vs->doc_view && interpreter->vs->doc_view->session && interpreter->vs->doc_view->session->tab); if_assert_failed goto reported; term = interpreter->vs->doc_view->session->tab->term; #ifdef CONFIG_LEDS set_led_value(interpreter->vs->doc_view->session->status.ecmascript_led, 'J'); #endif if (!get_opt_bool("ecmascript.error_reporting") || !init_string(&msg)) goto reported; strict = JSREPORT_IS_STRICT(report->flags) ? " strict" : ""; exception = JSREPORT_IS_EXCEPTION(report->flags) ? " exception" : ""; warning = JSREPORT_IS_WARNING(report->flags) ? " warning" : ""; error = !report->flags ? " error" : ""; add_format_to_string(&msg, _("A script embedded in the current " "document raised the following%s%s%s%s", term), strict, exception, warning, error); add_to_string(&msg, ":\n\n"); add_to_string(&msg, message); if (report->linebuf && report->tokenptr) { int pos = report->tokenptr - report->linebuf; add_format_to_string(&msg, "\n\n%s\n.%*s^%*s.", report->linebuf, pos - 2, " ", strlen(report->linebuf) - pos - 1, " "); } info_box(term, MSGBOX_FREE_TEXT, N_("JavaScript Error"), ALIGN_CENTER, msg.source); reported: /* Im clu'les. --pasky */ JS_ClearPendingException(ctx); } static JSBool safeguard(JSContext *ctx, JSScript *script) { struct ecmascript_interpreter *interpreter = JS_GetContextPrivate(ctx); int max_exec_time = get_opt_int("ecmascript.max_exec_time"); if (time(NULL) - interpreter->exec_start > max_exec_time) { struct terminal *term = interpreter->vs->doc_view->session->tab->term; /* A killer script! Alert! */ info_box(term, MSGBOX_FREE_TEXT, N_("JavaScript Emergency"), ALIGN_LEFT, msg_text(term, N_("A script embedded in the current document was running\n" "for more than %d seconds. This probably means there is\n" "a bug in the script and it could have halted the whole\n" "ELinks, so the script execution was interrupted."), max_exec_time)); return JS_FALSE; } return JS_TRUE; } static void setup_safeguard(struct ecmascript_interpreter *interpreter, JSContext *ctx) { interpreter->exec_start = time(NULL); JS_SetBranchCallback(ctx, safeguard); } void spidermonkey_init(void) { jsrt = JS_NewRuntime(0x400000UL); /* XXX: This is a hack to avoid a crash on exit. SMJS will crash * on JS_DestroyRuntime if the given runtime has never had any context * created, which will be the case if one closes ELinks without having * loaded any documents. */ JS_DestroyContext(JS_NewContext(jsrt, 0)); } void spidermonkey_done(void) { JS_DestroyRuntime(jsrt); JS_ShutDown(); } void * spidermonkey_get_interpreter(struct ecmascript_interpreter *interpreter) { JSContext *ctx; JSObject *window_obj, *document_obj, *forms_obj, *history_obj, *location_obj, *statusbar_obj, *menubar_obj, *navigator_obj; assert(interpreter); ctx = JS_NewContext(jsrt, 8192 /* Stack allocation chunk size */); if (!ctx) return NULL; interpreter->backend_data = ctx; JS_SetContextPrivate(ctx, interpreter); /* TODO: Make JSOPTION_STRICT and JSOPTION_WERROR configurable. */ #ifndef JSOPTION_COMPILE_N_GO #define JSOPTION_COMPILE_N_GO 0 /* Older SM versions don't have it. */ #endif /* XXX: JSOPTION_COMPILE_N_GO will go (will it?) when we implement * 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); window_obj = JS_NewObject(ctx, (JSClass *) &window_class, NULL, NULL); if (!window_obj) { spidermonkey_put_interpreter(interpreter); return NULL; } if (window_obj != NULL ) { JS_InitStandardClasses(ctx, window_obj); } JS_DefineProperties(ctx, window_obj, (JSPropertySpec *) window_props); spidermonkey_DefineFunctions(ctx, window_obj, window_funcs); if(ctx!=NULL && window_obj!=NULL && interpreter->vs!=NULL) { JS_SetPrivate(ctx, window_obj, interpreter->vs); /* to @window_class */ } document_obj = spidermonkey_InitClass(ctx, window_obj, NULL, (JSClass *) &document_class, NULL, 0, (JSPropertySpec *) document_props, document_funcs, NULL, NULL); if(document_obj!=NULL) { forms_obj = spidermonkey_InitClass(ctx, document_obj, NULL, (JSClass *) &forms_class, NULL, 0, (JSPropertySpec *) forms_props, forms_funcs, NULL, NULL); } history_obj = spidermonkey_InitClass(ctx, window_obj, NULL, (JSClass *) &history_class, NULL, 0, (JSPropertySpec *) NULL, history_funcs, NULL, NULL); location_obj = spidermonkey_InitClass(ctx, window_obj, NULL, (JSClass *) &location_class, NULL, 0, (JSPropertySpec *) location_props, location_funcs, NULL, NULL); menubar_obj = JS_InitClass(ctx, window_obj, NULL, (JSClass *) &menubar_class, NULL, 0, (JSPropertySpec *) unibar_props, NULL, NULL, NULL); if(menubar_obj!=NULL) { JS_SetPrivate(ctx, menubar_obj, "t"); /* to @menubar_class */ } statusbar_obj = JS_InitClass(ctx, window_obj, NULL, (JSClass *) &statusbar_class, NULL, 0, (JSPropertySpec *) unibar_props, NULL, NULL, NULL); if(statusbar_obj!=NULL){ JS_SetPrivate(ctx, statusbar_obj, "s"); /* to @statusbar_class */ } navigator_obj = JS_InitClass(ctx, window_obj, NULL, (JSClass *) &navigator_class, NULL, 0, (JSPropertySpec *) navigator_props, NULL, NULL, NULL); return ctx; } void spidermonkey_put_interpreter(struct ecmascript_interpreter *interpreter) { JSContext *ctx; assert(interpreter); ctx = interpreter->backend_data; JS_DestroyContext(ctx); interpreter->backend_data = NULL; } void spidermonkey_eval(struct ecmascript_interpreter *interpreter, struct string *code) { JSContext *ctx; jsval rval; assert(interpreter); ctx = interpreter->backend_data; setup_safeguard(interpreter, ctx); JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx), code->source, code->length, "", 0, &rval); } unsigned char * spidermonkey_eval_stringback(struct ecmascript_interpreter *interpreter, struct string *code) { JSContext *ctx; jsval rval; assert(interpreter); ctx = interpreter->backend_data; setup_safeguard(interpreter, ctx); if (JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx), code->source, code->length, "", 0, &rval) == JS_FALSE) { return NULL; } if (JSVAL_IS_VOID(rval)) { /* Undefined value. */ return NULL; } return stracpy(jsval_to_string(ctx, &rval)); } int spidermonkey_eval_boolback(struct ecmascript_interpreter *interpreter, struct string *code) { JSContext *ctx; jsval rval; int ret; assert(interpreter); ctx = interpreter->backend_data; setup_safeguard(interpreter, ctx); ret = JS_EvaluateScript(ctx, JS_GetGlobalObject(ctx), code->source, code->length, "", 0, &rval); if (ret == 2) { /* onClick="history.back()" */ return 0; } if (ret == JS_FALSE) { return -1; } if (JSVAL_IS_VOID(rval)) { /* Undefined value. */ return -1; } return jsval_to_boolean(ctx, &rval); }