From 636c31a3c78b7e0f5d2e63c6802baaa76c546c23 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Thu, 24 Apr 2014 01:59:38 -0700 Subject: [PATCH] Bug 716647 - Part 6 1/2: Add shell function to deterministically request interrupt. (r=jimb) --- js/src/jit-test/tests/debug/Frame-eval-19.js | 12 ++- js/src/shell/js.cpp | 87 ++++++++++++++++---- 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/js/src/jit-test/tests/debug/Frame-eval-19.js b/js/src/jit-test/tests/debug/Frame-eval-19.js index 2f24d36b321..568c263f785 100644 --- a/js/src/jit-test/tests/debug/Frame-eval-19.js +++ b/js/src/jit-test/tests/debug/Frame-eval-19.js @@ -2,18 +2,22 @@ load(libdir + "jitopts.js"); -if (!jitTogglesMatch(Opts_Ion2NoParallelCompilation)) +if (!jitTogglesMatch(Opts_IonEagerNoParallelCompilation)) quit(0); -withJitOptions(Opts_Ion2NoParallelCompilation, function () { +withJitOptions(Opts_IonEagerNoParallelCompilation, function () { var g = newGlobal(); var dbg = new Debugger; g.eval("" + function f(d) { g(d); }); g.eval("" + function g(d) { h(d); }); - g.eval("" + function h(d) { while (d); }); + g.eval("" + function h(d) { + var i = 0; + while (d) + interruptIf(d && i++ == 4000); + }); - timeout(5, function () { + setInterruptCallback(function () { dbg.addDebuggee(g); var frame = dbg.getNewestFrame(); if (frame.callee.name != "h" || frame.implementation != "ion") diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 91046039c01..186d1319853 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -112,8 +112,8 @@ static size_t gMaxStackSize = 128 * sizeof(size_t) * 1024; */ static double MAX_TIMEOUT_INTERVAL = 1800.0; static double gTimeoutInterval = -1.0; -static volatile bool gTimedOut = false; -static Maybe gTimeoutFunc; +static volatile bool gServiceInterrupt = false; +static Maybe gInterruptFunc; static bool enableDisassemblyDumps = false; @@ -335,7 +335,7 @@ static JSShellContextData * NewContextData() { /* Prevent creation of new contexts after we have been canceled. */ - if (gTimedOut) + if (gServiceInterrupt) return nullptr; JSShellContextData *data = (JSShellContextData *) @@ -358,16 +358,16 @@ GetContextData(JSContext *cx) static bool ShellInterruptCallback(JSContext *cx) { - if (!gTimedOut) + if (!gServiceInterrupt) return true; bool result; - RootedValue timeoutFunc(cx, gTimeoutFunc.ref()); - if (!timeoutFunc.isNull()) { + RootedValue interruptFunc(cx, gInterruptFunc.ref()); + if (!interruptFunc.isNull()) { JS::AutoSaveExceptionState savedExc(cx); - JSAutoCompartment ac(cx, &timeoutFunc.toObject()); + JSAutoCompartment ac(cx, &interruptFunc.toObject()); RootedValue rval(cx); - if (!JS_CallFunctionValue(cx, JS::NullPtr(), timeoutFunc, + if (!JS_CallFunctionValue(cx, JS::NullPtr(), interruptFunc, JS::HandleValueArray::empty(), &rval)) { return false; @@ -451,7 +451,7 @@ RunFile(JSContext *cx, Handle obj, const char *filename, FILE *file, #endif if (script && !compileOnly) { if (!JS_ExecuteScript(cx, obj, script)) { - if (!gQuitting && !gTimedOut) + if (!gQuitting && !gServiceInterrupt) gExitCode = EXITCODE_RUNTIME_ERROR; } int64_t t2 = PRMJ_Now() - t1; @@ -514,7 +514,7 @@ ReadEvalPrintLoop(JSContext *cx, Handle global, FILE *in, FILE *out, CharBuffer buffer(cx); do { ScheduleWatchdog(cx->runtime(), -1); - gTimedOut = false; + gServiceInterrupt = false; errno = 0; char *line = GetLine(in, startline == lineno ? "js> " : ""); @@ -3081,7 +3081,7 @@ Sleep_fn(JSContext *cx, unsigned argc, Value *vp) int64_t to_wakeup = PRMJ_Now() + t_ticks; for (;;) { PR_WaitCondVar(gSleepWakeup, t_ticks); - if (gTimedOut) + if (gServiceInterrupt) break; int64_t now = PRMJ_Now(); if (!IsBefore(now, to_wakeup)) @@ -3089,7 +3089,7 @@ Sleep_fn(JSContext *cx, unsigned argc, Value *vp) t_ticks = to_wakeup - now; } PR_Unlock(gWatchdogLock); - return !gTimedOut; + return !gServiceInterrupt; } static bool @@ -3281,10 +3281,10 @@ ScheduleWatchdog(JSRuntime *rt, double t) static void CancelExecution(JSRuntime *rt) { - gTimedOut = true; + gServiceInterrupt = true; JS_RequestInterruptCallback(rt); - if (!gTimeoutFunc.ref().get().isNull()) { + if (!gInterruptFunc.ref().get().isNull()) { static const char msg[] = "Script runs for too long, terminating.\n"; #if defined(XP_UNIX) && !defined(JS_THREADSAFE) /* It is not safe to call fputs from signals. */ @@ -3338,13 +3338,53 @@ Timeout(JSContext *cx, unsigned argc, Value *vp) JS_ReportError(cx, "Second argument must be a timeout function"); return false; } - gTimeoutFunc.ref() = value; + gInterruptFunc.ref() = value; } args.rval().setUndefined(); return SetTimeoutValue(cx, t); } +static bool +InterruptIf(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 1) { + JS_ReportError(cx, "Wrong number of arguments"); + return false; + } + + if (ToBoolean(args[0])) { + gServiceInterrupt = true; + JS_RequestInterruptCallback(cx->runtime()); + } + + args.rval().setUndefined(); + return true; +} + +static bool +SetInterruptCallback(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() != 1) { + JS_ReportError(cx, "Wrong number of arguments"); + return false; + } + + RootedValue value(cx, args[0]); + if (!value.isObject() || !value.toObject().is()) { + JS_ReportError(cx, "Argument must be a function"); + return false; + } + gInterruptFunc.ref() = value; + + args.rval().setUndefined(); + return true; +} + static bool Elapsed(JSContext *cx, unsigned argc, jsval *vp) { @@ -4598,7 +4638,18 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "timeout([seconds], [func])", " Get/Set the limit in seconds for the execution time for the current context.\n" " A negative value (default) means that the execution time is unlimited.\n" -" If a second argument is provided, it will be invoked when the timer elapses.\n"), +" If a second argument is provided, it will be invoked when the timer elapses.\n" +" Calling this function will replace any callback set by |setInterruptCallback|.\n"), + + JS_FN_HELP("interruptIf", InterruptIf, 1, 0, +"interruptIf(cond)", +" Requests interrupt callback if cond is true. If a callback function is set via\n" +" |timeout| or |setInterruptCallback|, it will be called. No-op otherwise."), + + JS_FN_HELP("setInterruptCallback", SetInterruptCallback, 1, 0, +"setInterruptCallback(func)", +" Sets func as the interrupt callback function.\n" +" Calling this function will replace any callback set by |timeout|.\n"), JS_FN_HELP("elapsed", Elapsed, 0, 0, "elapsed()", @@ -6134,7 +6185,7 @@ main(int argc, char **argv, char **envp) if (!SetRuntimeOptions(rt, op)) return 1; - gTimeoutFunc.construct(rt, NullValue()); + gInterruptFunc.construct(rt, NullValue()); JS_SetGCParameter(rt, JSGC_MAX_BYTES, 0xffffffff); #ifdef JSGC_GENERATIONAL @@ -6184,7 +6235,7 @@ main(int argc, char **argv, char **envp) KillWatchdog(); - gTimeoutFunc.destroy(); + gInterruptFunc.destroy(); #ifdef JS_THREADSAFE for (size_t i = 0; i < workerThreads.length(); i++)