diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 11a2f32ba2f..92df2d1debf 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5336,6 +5336,17 @@ JS_TriggerOperationCallback(JSContext *cx) JS_ATOMIC_SET(&cx->operationCallbackFlag, 1); } +JS_PUBLIC_API(void) +JS_TriggerAllOperationCallbacks(JSRuntime *rt) +{ + JSContext *acx, *iter; + JS_LOCK_GC(rt); + iter = NULL; + while ((acx = js_ContextIterator(rt, JS_FALSE, &iter))) + JS_TriggerOperationCallback(acx); + JS_UNLOCK_GC(rt); +} + JS_PUBLIC_API(JSBool) JS_IsRunning(JSContext *cx) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index dbd833c70e0..2a5bce5f473 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2281,6 +2281,9 @@ JS_GetOperationCallback(JSContext *cx); extern JS_PUBLIC_API(void) JS_TriggerOperationCallback(JSContext *cx); +extern JS_PUBLIC_API(void) +JS_TriggerAllOperationCallbacks(JSRuntime *rt); + extern JS_PUBLIC_API(JSBool) JS_IsRunning(JSContext *cx); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 23cb7f1c6cd..9cc5abacb81 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -43,6 +43,7 @@ */ #include "jsstddef.h" #include +#include #include #include #include @@ -98,7 +99,8 @@ typedef enum JSShellExitCode { EXITCODE_RUNTIME_ERROR = 3, EXITCODE_FILE_NOT_FOUND = 4, - EXITCODE_OUT_OF_MEMORY = 5 + EXITCODE_OUT_OF_MEMORY = 5, + EXITCODE_TIMEOUT = 6 } JSShellExitCode; size_t gStackChunkSize = 8192; @@ -109,26 +111,41 @@ static jsuword gStackBase; static size_t gScriptStackQuota = JS_DEFAULT_SCRIPT_STACK_QUOTA; -static uint32 gOperationLimit = 0; +/* + * Limit the timeout to 30 minutes to prevent an overflow on platfoms + * that represent the time internally in microseconds using 32-bit int. + */ +static jsdouble MAX_TIMEOUT_INTERVAL = 1800.0; +static jsdouble gTimeoutInterval = -1.0; +static volatile bool gCanceled = false; static JSBool SetTimeoutValue(JSContext *cx, jsdouble t); -static double -GetTimeoutValue(JSContext *cx); +static bool +InitWatchdog(JSRuntime *rt); static void -StopWatchdog(JSRuntime *rt); +KillWatchdog(); -static JSBool -StartWatchdog(JSRuntime *rt); +static bool +ScheduleWatchdog(JSRuntime *rt, jsdouble t); + +static void +CancelExecution(JSRuntime *rt); /* * Watchdog thread state. */ #ifdef JS_THREADSAFE + +static PRLock *gWatchdogLock = NULL; static PRCondVar *gWatchdogWakeup = NULL; static PRThread *gWatchdogThread = NULL; +static bool gWatchdogHasTimeout = false; +static PRIntervalTime gWatchdogTimeout = 0; + +static PRCondVar *gSleepWakeup = NULL; /* * Holding the gcLock already guarantees that the context list is locked when @@ -141,20 +158,19 @@ static PRThread *gWatchdogThread = NULL; JS_END_MACRO #else + static JSRuntime *gRuntime = NULL; /* * Since signal handlers can't block, we must disable them before manipulating * the context list. */ - -#define WITH_LOCKED_CONTEXT_LIST(x) \ - JS_BEGIN_MACRO \ - StopWatchdog(gRuntime); \ - x; \ - StartWatchdog(gRuntime); \ +#define WITH_LOCKED_CONTEXT_LIST(x) \ + JS_BEGIN_MACRO \ + ScheduleWatchdog(gRuntime, -1); \ + x; \ + ScheduleWatchdog(gRuntime, gTimeoutInterval); \ JS_END_MACRO - #endif int gExitCode = 0; @@ -263,6 +279,10 @@ struct JSShellContextData { static JSShellContextData * NewContextData() { + /* Prevent creation of new contexts after we have been canceled. */ + if (gCanceled) + return NULL; + JSShellContextData *data = (JSShellContextData *) calloc(sizeof(JSShellContextData), 1); if (!data) @@ -283,12 +303,7 @@ GetContextData(JSContext *cx) static JSBool ShellOperationCallback(JSContext *cx) { - JSShellContextData *data; - if ((data = GetContextData(cx)) != NULL) { - /* If we spent too much time in this script, abort it. */ - return !gOperationLimit || (uint32(js_IntervalNow() - data->startTime) < gOperationLimit); - } - return JS_TRUE; + return !gCanceled; } static void @@ -383,8 +398,6 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY) buffer = NULL; size = 0; /* assign here to avoid warnings */ do { - size_t len = 0; /* initialize to avoid warnings */ - /* * Accumulate lines until we get a 'compilable unit' - one that either * generates an error (before running out of source) or that compiles @@ -392,7 +405,13 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY) * coincides with the end of a line. */ startline = lineno; + size_t len = 0; /* initialize to avoid warnings */ do { + ScheduleWatchdog(cx->runtime, -1); +#ifdef JS_THREADSAFE + jsrefcount rc = JS_SuspendRequest(cx); +#endif + gCanceled = false; errno = 0; char *line = GetLine(file, startline == lineno ? "js> " : ""); if (!line) { @@ -432,6 +451,13 @@ Process(JSContext *cx, JSObject *obj, char *filename, JSBool forceTTY) free(line); } lineno++; +#ifdef JS_THREADSAFE + JS_ResumeRequest(cx, rc); +#endif + if (!ScheduleWatchdog(cx->runtime, gTimeoutInterval)) { + hitEOF = JS_TRUE; + break; + } } while (!JS_BufferIsCompilableUnit(cx, obj, buffer, len)); if (hitEOF && !buffer) @@ -693,6 +719,8 @@ extern void js_InitJITStatsClass(JSContext *cx, JSObject *glob); return usage(); Process(cx, obj, argv[i], JS_FALSE); + if (gExitCode != 0) + return gExitCode; /* * XXX: js -f foo.js should interpret foo.js and then @@ -2808,27 +2836,59 @@ ShapeOf(JSContext *cx, uintN argc, jsval *vp) #ifdef JS_THREADSAFE +/* + * Check that t1 comes strictly before t2. The function correctly deals with + * PRIntervalTime wrap-around between t2 and t1 assuming that t2 and t1 stays + * within INT32_MAX from each other. We use MAX_TIMEOUT_INTERVAL to enforce + * this restriction. + */ +static bool +IsBefore(PRIntervalTime t1, PRIntervalTime t2) +{ + return int32(t1 - t2) < 0; +} + static JSBool Sleep_fn(JSContext *cx, uintN argc, jsval *vp) { - jsdouble t_secs; - PRUint32 t_ticks; - jsrefcount rc; + PRIntervalTime t_ticks; - if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs)) - return JS_FALSE; + if (argc == 0) { + t_ticks = 0; + } else { + jsdouble t_secs; - if (t_secs < 0 || JSDOUBLE_IS_NaN(t_secs)) - t_secs = 0; + if (!JS_ValueToNumber(cx, argc == 0 ? JSVAL_VOID : vp[2], &t_secs)) + return JS_FALSE; - rc = JS_SuspendRequest(cx); - t_ticks = (PRUint32)(PR_TicksPerSecond() * t_secs); - if (PR_Sleep(t_ticks) == PR_SUCCESS) - *vp = JSVAL_TRUE; - else - *vp = JSVAL_FALSE; - JS_ResumeRequest(cx, rc); - return JS_TRUE; + /* NB: The next condition also filter out NaNs. */ + if (!(t_secs <= MAX_TIMEOUT_INTERVAL)) { + JS_ReportError(cx, "Excessive sleep interval"); + return JS_FALSE; + } + t_ticks = (t_secs <= 0.0) + ? 0 + : PRIntervalTime(PR_TicksPerSecond() * t_secs); + } + if (t_ticks == 0) { + JS_YieldRequest(cx); + } else { + jsrefcount rc = JS_SuspendRequest(cx); + PR_Lock(gWatchdogLock); + PRIntervalTime to_wakeup = PR_IntervalNow() + t_ticks; + for (;;) { + PR_WaitCondVar(gSleepWakeup, t_ticks); + if (gCanceled) + break; + PRIntervalTime now = PR_IntervalNow(); + if (!IsBefore(now, to_wakeup)) + break; + t_ticks = to_wakeup - now; + } + PR_Unlock(gWatchdogLock); + JS_ResumeRequest(cx, rc); + } + return !gCanceled; } typedef struct ScatterThreadData ScatterThreadData; @@ -3065,165 +3125,222 @@ fail: goto out; } +static bool +InitWatchdog(JSRuntime *rt) +{ + JS_ASSERT(!gWatchdogThread); + gWatchdogLock = PR_NewLock(); + if (gWatchdogLock) { + gWatchdogWakeup = PR_NewCondVar(gWatchdogLock); + if (gWatchdogWakeup) { + gSleepWakeup = PR_NewCondVar(gWatchdogLock); + if (gSleepWakeup) + return true; + PR_DestroyCondVar(gWatchdogWakeup); + } + PR_DestroyLock(gWatchdogLock); + } + return false; +} + +static void +KillWatchdog() +{ + PRThread *thread; + + PR_Lock(gWatchdogLock); + thread = gWatchdogThread; + if (thread) { + /* + * The watchdog thread is running, tell it to terminate waking it up + * if necessary. + */ + gWatchdogThread = NULL; + PR_NotifyCondVar(gWatchdogWakeup); + } + PR_Unlock(gWatchdogLock); + if (thread) + PR_JoinThread(thread); + PR_DestroyCondVar(gSleepWakeup); + PR_DestroyCondVar(gWatchdogWakeup); + PR_DestroyLock(gWatchdogLock); +} + static void WatchdogMain(void *arg) { JSRuntime *rt = (JSRuntime *) arg; - JS_LOCK_GC(rt); + PR_Lock(gWatchdogLock); while (gWatchdogThread) { - JSContext *acx = NULL; - - while ((acx = js_NextActiveContext(rt, acx))) - JS_TriggerOperationCallback(acx); + PRIntervalTime now = PR_IntervalNow(); + if (gWatchdogHasTimeout && !IsBefore(now, gWatchdogTimeout)) { + /* + * The timeout has just expired. Trigger the operation callback + * outside the lock. + */ + gWatchdogHasTimeout = false; + PR_Unlock(gWatchdogLock); + CancelExecution(rt); + PR_Lock(gWatchdogLock); + /* Wake up any threads doing sleep. */ + PR_NotifyAllCondVar(gSleepWakeup); + } else { + PRIntervalTime sleepDuration = gWatchdogHasTimeout + ? gWatchdogTimeout - now + : PR_INTERVAL_NO_TIMEOUT; #ifdef DEBUG - PRStatus status = + PRStatus status = #endif - /* Trigger the operation callbacks every second. */ - PR_WaitCondVar(gWatchdogWakeup, PR_SecondsToInterval(1)); - JS_ASSERT(status == PR_SUCCESS); + PR_WaitCondVar(gWatchdogWakeup, sleepDuration); + JS_ASSERT(status == PR_SUCCESS); + } } - /* Wake up the main thread waiting for the watchdog to terminate. */ - PR_NotifyCondVar(gWatchdogWakeup); - JS_UNLOCK_GC(rt); + PR_Unlock(gWatchdogLock); } -static JSBool -StartWatchdog(JSRuntime *rt) +static bool +ScheduleWatchdog(JSRuntime *rt, jsdouble t) { - if (gWatchdogThread || !gOperationLimit) - return JS_TRUE; - - JS_LOCK_GC(rt); - gWatchdogThread = PR_CreateThread(PR_USER_THREAD, - WatchdogMain, - rt, - PR_PRIORITY_NORMAL, - PR_LOCAL_THREAD, - PR_UNJOINABLE_THREAD, - 0); + if (t <= 0) { + PR_Lock(gWatchdogLock); + gWatchdogHasTimeout = false; + PR_Unlock(gWatchdogLock); + return true; + } + + PRIntervalTime interval = PRIntervalTime(ceil(t * PR_TicksPerSecond())); + PRIntervalTime timeout = PR_IntervalNow() + interval; + PR_Lock(gWatchdogLock); if (!gWatchdogThread) { - JS_UNLOCK_GC(rt); - return JS_FALSE; + JS_ASSERT(!gWatchdogHasTimeout); + gWatchdogThread = PR_CreateThread(PR_USER_THREAD, + WatchdogMain, + rt, + PR_PRIORITY_NORMAL, + PR_LOCAL_THREAD, + PR_JOINABLE_THREAD, + 0); + if (!gWatchdogThread) { + PR_Unlock(gWatchdogLock); + return false; + } + } else if (!gWatchdogHasTimeout || IsBefore(timeout, gWatchdogTimeout)) { + PR_NotifyCondVar(gWatchdogWakeup); } - JS_UNLOCK_GC(rt); - return JS_TRUE; + gWatchdogHasTimeout = true; + gWatchdogTimeout = timeout; + PR_Unlock(gWatchdogLock); + return true; } -static void -StopWatchdog(JSRuntime *rt) -{ - JS_LOCK_GC(rt); - if (gWatchdogThread) { - /* - * The watchdog thread is running, tell it to terminate waking it up - * if necessary and wait until it signals that it done. - */ - gWatchdogThread = NULL; - PR_NotifyCondVar(gWatchdogWakeup); - PR_WaitCondVar(gWatchdogWakeup, PR_INTERVAL_NO_TIMEOUT); - } - JS_UNLOCK_GC(rt); - JS_DESTROY_CONDVAR(gWatchdogWakeup); -} - -#else - -static void -WatchdogHandler(int sig) -{ - JSRuntime *rt = gRuntime; - JSContext *acx = NULL; - - while ((acx = js_NextActiveContext(rt, acx))) - JS_TriggerOperationCallback(acx); - -#ifndef XP_WIN - alarm(1); -#endif -} +#else /* !JS_THREADSAFE */ #ifdef XP_WIN static HANDLE gTimerHandle = 0; -VOID CALLBACK TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) +VOID CALLBACK +TimerCallback(PVOID lpParameter, BOOLEAN TimerOrWaitFired) { - WatchdogHandler(0); + CancelExecution(rt); } + +#else + +static void +AlarmHandler(int sig) +{ + CancelExecution(gRuntime); +} + #endif -static JSBool -StartWatchdog(JSRuntime *rt) +static bool +InitWatchdog(JSRuntime *rt) { - if (!gOperationLimit) - return JS_TRUE; - -#ifdef XP_WIN - JS_ASSERT(gTimerHandle == 0); - if (!CreateTimerQueueTimer(&gTimerHandle, - NULL, - (WAITORTIMERCALLBACK)TimerCallback, - NULL, - 1000, - 1000, - WT_EXECUTEINTIMERTHREAD)) - return JS_FALSE; -#else - signal(SIGALRM, WatchdogHandler); /* set the Alarm signal capture */ - alarm(1); -#endif - - return JS_TRUE; + gRuntime = rt; + return true; } static void -StopWatchdog(JSRuntime *rt) +KillWatchdog() { -#ifdef XP_WIN - DeleteTimerQueueTimer(NULL, gTimerHandle, NULL); - gTimerHandle = 0; -#else - alarm(0); - signal(SIGALRM, NULL); -#endif + ScheduleWatchdog(gRuntime, -1); } -#endif /* JS_THREADSAFE */ +static bool +ScheduleWatchdog(JSRuntime *rt, jsdouble t) +{ +#ifdef XP_WIN + if (gTimerHandle) { + DeleteTimerQueueTimer(NULL, gTimerHandle, NULL); + gTimerHandle = 0; + } + if (t > 0 && + !CreateTimerQueueTimer(&gTimerHandle, + NULL, + (WAITORTIMERCALLBACK)TimerCallback, + rt, + DWORD(ceil(t * 1000.0)), + 0, + WT_EXECUTEINTIMERTHREAD | WT_EXECUTEONLYONCE)) { + gTimerHandle = 0; + return false; + } +#else + /* FIXME: use setitimer when available for sub-second resolution. */ + if (t <= 0) { + alarm(0); + signal(SIGALRM, NULL); + } else { + signal(SIGALRM, AlarmHandler); /* set the Alarm signal capture */ + alarm(ceil(t)); + } +#endif + return true; +} + +#endif /* !JS_THREADSAFE */ + +static void +CancelExecution(JSRuntime *rt) +{ + gCanceled = true; + if (gExitCode == 0) + gExitCode = EXITCODE_TIMEOUT; + JS_TriggerAllOperationCallbacks(rt); + + 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. */ + write(2, msg, sizeof(msg) - 1); +#else + fputs(msg, stderr); +#endif +} static JSBool SetTimeoutValue(JSContext *cx, jsdouble t) { /* NB: The next condition also filter out NaNs. */ - if (!(t <= 3600.0)) { + if (!(t <= MAX_TIMEOUT_INTERVAL)) { JS_ReportError(cx, "Excessive timeout value"); return JS_FALSE; } - - gOperationLimit = (t > 0) ? JSInt64(t*1000) : 0; - - if (!StartWatchdog(cx->runtime)) { - JS_ReportError(cx, "failed to create the watchdog"); + gTimeoutInterval = t; + if (!ScheduleWatchdog(cx->runtime, t)) { + JS_ReportError(cx, "Failed to create the watchdog"); return JS_FALSE; } - return JS_TRUE; } -static double -GetTimeoutValue(JSContext *cx) -{ - if (!gOperationLimit) - return -1; - - return gOperationLimit/PRMJ_USEC_PER_MSEC; -} - static JSBool Timeout(JSContext *cx, uintN argc, jsval *vp) { if (argc == 0) - return JS_NewNumberValue(cx, GetTimeoutValue(cx), vp); + return JS_NewNumberValue(cx, gTimeoutInterval, vp); if (argc > 1) { JS_ReportError(cx, "Wrong number of arguments"); @@ -4377,13 +4494,8 @@ main(int argc, char **argv, char **envp) if (!rt) return 1; -#ifdef JS_THREADSAFE - gWatchdogWakeup = JS_NEW_CONDVAR(rt->gcLock); - if (!gWatchdogWakeup) + if (!InitWatchdog(rt)) return 1; -#else - gRuntime = rt; -#endif JS_SetContextCallback(rt, ContextCallback); @@ -4498,7 +4610,7 @@ main(int argc, char **argv, char **envp) JS_DestroyContext(cx) ); - StopWatchdog(rt); + KillWatchdog(); JS_DestroyRuntime(rt); JS_ShutDown();