ExecutionTimeLimitTest.cpp [plain text]
#include "config.h"
#include "ExecutionTimeLimitTest.h"
#include "InitializeThreading.h"
#include "JSContextRefPrivate.h"
#include "JavaScriptCore.h"
#include "Options.h"
#include <chrono>
#include <wtf/CurrentTime.h>
#include <wtf/text/StringBuilder.h>
using namespace std::chrono;
using JSC::Options;
static JSGlobalContextRef context = nullptr;
static JSValueRef currentCPUTimeAsJSFunctionCallback(JSContextRef ctx, JSObjectRef functionObject, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
UNUSED_PARAM(functionObject);
UNUSED_PARAM(thisObject);
UNUSED_PARAM(argumentCount);
UNUSED_PARAM(arguments);
UNUSED_PARAM(exception);
ASSERT(JSContextGetGlobalContext(ctx) == context);
return JSValueMakeNumber(ctx, currentCPUTime().count() / 1000000.);
}
bool shouldTerminateCallbackWasCalled = false;
static bool shouldTerminateCallback(JSContextRef, void*)
{
shouldTerminateCallbackWasCalled = true;
return true;
}
bool cancelTerminateCallbackWasCalled = false;
static bool cancelTerminateCallback(JSContextRef, void*)
{
cancelTerminateCallbackWasCalled = true;
return false;
}
int extendTerminateCallbackCalled = 0;
static bool extendTerminateCallback(JSContextRef ctx, void*)
{
extendTerminateCallbackCalled++;
if (extendTerminateCallbackCalled == 1) {
JSContextGroupRef contextGroup = JSContextGetGroup(ctx);
JSContextGroupSetExecutionTimeLimit(contextGroup, .200f, extendTerminateCallback, 0);
return false;
}
return true;
}
struct TierOptions {
const char* tier;
unsigned timeLimitAdjustmentMillis;
const char* optionsStr;
};
static void testResetAfterTimeout(bool& failed)
{
JSValueRef v = nullptr;
JSValueRef exception = nullptr;
const char* reentryScript = "100";
JSStringRef script = JSStringCreateWithUTF8CString(reentryScript);
v = JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
if (exception) {
printf("FAIL: Watchdog timeout was not reset.\n");
failed = true;
} else if (!JSValueIsNumber(context, v) || JSValueToNumber(context, v, nullptr) != 100) {
printf("FAIL: Script result is not as expected.\n");
failed = true;
}
}
int testExecutionTimeLimit()
{
static const TierOptions tierOptionsList[] = {
{ "LLINT", 0, "--useConcurrentJIT=false --useLLInt=true --useJIT=false" },
{ "Baseline", 0, "--useConcurrentJIT=false --useLLInt=true --useJIT=true --useDFGJIT=false" },
{ "DFG", 0, "--useConcurrentJIT=false --useLLInt=true --useJIT=true --useDFGJIT=true --useFTLJIT=false" },
{ "FTL", 200, "--useConcurrentJIT=false --useLLInt=true --useJIT=true --useDFGJIT=true --useFTLJIT=true" },
};
bool failed = false;
JSC::initializeThreading();
Options::initialize();
for (auto tierOptions : tierOptionsList) {
StringBuilder savedOptionsBuilder;
Options::dumpAllOptionsInALine(savedOptionsBuilder);
Options::setOptions(tierOptions.optionsStr);
unsigned tierAdjustmentMillis = tierOptions.timeLimitAdjustmentMillis;
double timeLimit;
context = JSGlobalContextCreateInGroup(nullptr, nullptr);
JSContextGroupRef contextGroup = JSContextGetGroup(context);
JSObjectRef globalObject = JSContextGetGlobalObject(context);
ASSERT(JSValueIsObject(context, globalObject));
JSValueRef exception = nullptr;
JSStringRef currentCPUTimeStr = JSStringCreateWithUTF8CString("currentCPUTime");
JSObjectRef currentCPUTimeFunction = JSObjectMakeFunctionWithCallback(context, currentCPUTimeStr, currentCPUTimeAsJSFunctionCallback);
JSObjectSetProperty(context, globalObject, currentCPUTimeStr, currentCPUTimeFunction, kJSPropertyAttributeNone, nullptr);
JSStringRelease(currentCPUTimeStr);
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, shouldTerminateCallback, 0);
{
unsigned timeAfterWatchdogShouldHaveFired = 300 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("function foo() { var startTime = currentCPUTime(); while (true) { for (var i = 0; i < 1000; i++); if (currentCPUTime() - startTime > ");
scriptBuilder.appendNumber(timeAfterWatchdogShouldHaveFired / 1000.0);
scriptBuilder.append(") break; } } foo();");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
shouldTerminateCallbackWasCalled = false;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
if (((endTime - startTime) < milliseconds(timeAfterWatchdogShouldHaveFired)) && shouldTerminateCallbackWasCalled)
printf("PASS: %s script timed out as expected.\n", tierOptions.tier);
else {
if ((endTime - startTime) >= milliseconds(timeAfterWatchdogShouldHaveFired))
printf("FAIL: %s script did not time out as expected.\n", tierOptions.tier);
if (!shouldTerminateCallbackWasCalled)
printf("FAIL: %s script timeout callback was not called.\n", tierOptions.tier);
failed = true;
}
if (!exception) {
printf("FAIL: %s TerminatedExecutionException was not thrown.\n", tierOptions.tier);
failed = true;
}
testResetAfterTimeout(failed);
}
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, shouldTerminateCallback, 0);
{
unsigned timeAfterWatchdogShouldHaveFired = 300 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("var startTime = currentCPUTime();"
"function recurse(i) {"
"'use strict';"
"if (i % 1000 === 0) {"
"if (currentCPUTime() - startTime >");
scriptBuilder.appendNumber(timeAfterWatchdogShouldHaveFired / 1000.0);
scriptBuilder.append(" ) { return; }");
scriptBuilder.append(" }");
scriptBuilder.append(" return recurse(i + 1); }");
scriptBuilder.append("recurse(0);");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
shouldTerminateCallbackWasCalled = false;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
if (((endTime - startTime) < milliseconds(timeAfterWatchdogShouldHaveFired)) && shouldTerminateCallbackWasCalled)
printf("PASS: %s script with infinite tail calls timed out as expected .\n", tierOptions.tier);
else {
if ((endTime - startTime) >= milliseconds(timeAfterWatchdogShouldHaveFired))
printf("FAIL: %s script with infinite tail calls did not time out as expected.\n", tierOptions.tier);
if (!shouldTerminateCallbackWasCalled)
printf("FAIL: %s script with infinite tail calls' timeout callback was not called.\n", tierOptions.tier);
failed = true;
}
if (!exception) {
printf("FAIL: %s TerminatedExecutionException was not thrown.\n", tierOptions.tier);
failed = true;
}
testResetAfterTimeout(failed);
}
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, shouldTerminateCallback, 0);
{
unsigned timeAfterWatchdogShouldHaveFired = 300 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("function foo() { var startTime = currentCPUTime(); try { while (true) { for (var i = 0; i < 1000; i++); if (currentCPUTime() - startTime > ");
scriptBuilder.appendNumber(timeAfterWatchdogShouldHaveFired / 1000.0);
scriptBuilder.append(") break; } } catch(e) { } } foo();");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
shouldTerminateCallbackWasCalled = false;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
if (((endTime - startTime) >= milliseconds(timeAfterWatchdogShouldHaveFired)) || !shouldTerminateCallbackWasCalled) {
if (!((endTime - startTime) < milliseconds(timeAfterWatchdogShouldHaveFired)))
printf("FAIL: %s script did not time out as expected.\n", tierOptions.tier);
if (!shouldTerminateCallbackWasCalled)
printf("FAIL: %s script timeout callback was not called.\n", tierOptions.tier);
failed = true;
}
if (exception)
printf("PASS: %s TerminatedExecutionException was not catchable as expected.\n", tierOptions.tier);
else {
printf("FAIL: %s TerminatedExecutionException was caught.\n", tierOptions.tier);
failed = true;
}
testResetAfterTimeout(failed);
}
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, 0, 0);
{
unsigned timeAfterWatchdogShouldHaveFired = 300 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("function foo() { var startTime = currentCPUTime(); while (true) { for (var i = 0; i < 1000; i++); if (currentCPUTime() - startTime > ");
scriptBuilder.appendNumber(timeAfterWatchdogShouldHaveFired / 1000.0);
scriptBuilder.append(") break; } } foo();");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
shouldTerminateCallbackWasCalled = false;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
if (((endTime - startTime) < milliseconds(timeAfterWatchdogShouldHaveFired)) && !shouldTerminateCallbackWasCalled)
printf("PASS: %s script timed out as expected when no callback is specified.\n", tierOptions.tier);
else {
if ((endTime - startTime) >= milliseconds(timeAfterWatchdogShouldHaveFired))
printf("FAIL: %s script did not time out as expected when no callback is specified.\n", tierOptions.tier);
else
printf("FAIL: %s script called stale callback function.\n", tierOptions.tier);
failed = true;
}
if (!exception) {
printf("FAIL: %s TerminatedExecutionException was not thrown.\n", tierOptions.tier);
failed = true;
}
testResetAfterTimeout(failed);
}
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, cancelTerminateCallback, 0);
{
unsigned timeAfterWatchdogShouldHaveFired = 300 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("function foo() { var startTime = currentCPUTime(); while (true) { for (var i = 0; i < 1000; i++); if (currentCPUTime() - startTime > ");
scriptBuilder.appendNumber(timeAfterWatchdogShouldHaveFired / 1000.0);
scriptBuilder.append(") break; } } foo();");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
cancelTerminateCallbackWasCalled = false;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
if (((endTime - startTime) >= milliseconds(timeAfterWatchdogShouldHaveFired)) && cancelTerminateCallbackWasCalled && !exception)
printf("PASS: %s script timeout was cancelled as expected.\n", tierOptions.tier);
else {
if (((endTime - startTime) < milliseconds(timeAfterWatchdogShouldHaveFired)) || exception)
printf("FAIL: %s script timeout was not cancelled.\n", tierOptions.tier);
if (!cancelTerminateCallbackWasCalled)
printf("FAIL: %s script timeout callback was not called.\n", tierOptions.tier);
failed = true;
}
if (exception) {
printf("FAIL: %s Unexpected TerminatedExecutionException thrown.\n", tierOptions.tier);
failed = true;
}
}
timeLimit = (100 + tierAdjustmentMillis) / 1000.0;
JSContextGroupSetExecutionTimeLimit(contextGroup, timeLimit, extendTerminateCallback, 0);
{
unsigned timeBeforeExtendedDeadline = 250 + tierAdjustmentMillis;
unsigned timeAfterExtendedDeadline = 600 + tierAdjustmentMillis;
unsigned maxBusyLoopTime = 750 + tierAdjustmentMillis;
StringBuilder scriptBuilder;
scriptBuilder.append("function foo() { var startTime = currentCPUTime(); while (true) { for (var i = 0; i < 1000; i++); if (currentCPUTime() - startTime > ");
scriptBuilder.appendNumber(maxBusyLoopTime / 1000.0); scriptBuilder.append(") break; } } foo();");
JSStringRef script = JSStringCreateWithUTF8CString(scriptBuilder.toString().utf8().data());
exception = nullptr;
extendTerminateCallbackCalled = 0;
auto startTime = currentCPUTime();
JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
auto endTime = currentCPUTime();
auto deltaTime = endTime - startTime;
if ((deltaTime >= milliseconds(timeBeforeExtendedDeadline)) && (deltaTime < milliseconds(timeAfterExtendedDeadline)) && (extendTerminateCallbackCalled == 2) && exception)
printf("PASS: %s script timeout was extended as expected.\n", tierOptions.tier);
else {
if (deltaTime < milliseconds(timeBeforeExtendedDeadline))
printf("FAIL: %s script timeout was not extended as expected.\n", tierOptions.tier);
else if (deltaTime >= milliseconds(timeAfterExtendedDeadline))
printf("FAIL: %s script did not timeout.\n", tierOptions.tier);
if (extendTerminateCallbackCalled < 1)
printf("FAIL: %s script timeout callback was not called.\n", tierOptions.tier);
if (extendTerminateCallbackCalled < 2)
printf("FAIL: %s script timeout callback was not called after timeout extension.\n", tierOptions.tier);
if (!exception)
printf("FAIL: %s TerminatedExecutionException was not thrown during timeout extension test.\n", tierOptions.tier);
failed = true;
}
}
JSGlobalContextRelease(context);
Options::setOptions(savedOptionsBuilder.toString().ascii().data());
}
return failed;
}