/* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "PingPongStackOverflowTest.h" #include "InitializeThreading.h" #include "JSContextRefPrivate.h" #include "JavaScriptCore.h" #include "Options.h" #include using JSC::Options; static JSGlobalContextRef context = nullptr; static int nativeRecursionCount = 0; static bool PingPongStackOverflowObject_hasInstance(JSContextRef context, JSObjectRef constructor, JSValueRef possibleValue, JSValueRef* exception) { UNUSED_PARAM(context); UNUSED_PARAM(constructor); JSStringRef hasInstanceName = JSStringCreateWithUTF8CString("hasInstance"); JSValueRef hasInstance = JSObjectGetProperty(context, constructor, hasInstanceName, exception); JSStringRelease(hasInstanceName); if (!hasInstance) return false; int countAtEntry = nativeRecursionCount++; JSValueRef result = 0; if (nativeRecursionCount < 100) { JSObjectRef function = JSValueToObject(context, hasInstance, exception); result = JSObjectCallAsFunction(context, function, constructor, 1, &possibleValue, exception); } else { StringBuilder builder; builder.append("dummy.valueOf([0]"); for (int i = 1; i < 35000; i++) { builder.append(", ["); builder.appendNumber(i); builder.append("]"); } builder.append(");"); JSStringRef script = JSStringCreateWithUTF8CString(builder.toString().utf8().data()); result = JSEvaluateScript(context, script, NULL, NULL, 1, exception); JSStringRelease(script); } --nativeRecursionCount; if (nativeRecursionCount != countAtEntry) printf(" ERROR: PingPongStackOverflow test saw a recursion count mismatch\n"); return result && JSValueToBoolean(context, result); } JSClassDefinition PingPongStackOverflowObject_definition = { 0, kJSClassAttributeNone, "PingPongStackOverflowObject", NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, PingPongStackOverflowObject_hasInstance, NULL, }; static JSClassRef PingPongStackOverflowObject_class(JSContextRef context) { UNUSED_PARAM(context); static JSClassRef jsClass; if (!jsClass) jsClass = JSClassCreate(&PingPongStackOverflowObject_definition); return jsClass; } // This tests tests a stack overflow on VM reentry into a JS function from a native function // after ping-pong'ing back and forth between JS and native functions multiple times. // This test should not hang or crash. int testPingPongStackOverflow() { bool failed = false; JSC::initializeThreading(); Options::initialize(); // Ensure options is initialized first. auto origReservedZoneSize = Options::reservedZoneSize(); auto origErrorModeReservedZoneSize = Options::errorModeReservedZoneSize(); auto origUseLLInt = Options::useLLInt(); auto origMaxPerThreadStackUsage = Options::maxPerThreadStackUsage(); Options::reservedZoneSize() = 128 * KB; Options::errorModeReservedZoneSize() = 64 * KB; #if ENABLE(JIT) // Normally, we want to disable the LLINT to force the use of JITted code which is necessary for // reproducing the regression in https://bugs.webkit.org/show_bug.cgi?id=148749. However, we only // want to do this if the LLINT isn't the only available execution engine. Options::useLLInt() = false; #endif const char* scriptString = "var count = 0;" \ "PingPongStackOverflowObject.hasInstance = function f() {" \ " return (undefined instanceof PingPongStackOverflowObject);" \ "};" \ "PingPongStackOverflowObject.__proto__ = undefined;" \ "undefined instanceof PingPongStackOverflowObject;"; JSValueRef scriptResult = nullptr; JSValueRef exception = nullptr; JSStringRef script = JSStringCreateWithUTF8CString(scriptString); nativeRecursionCount = 0; context = JSGlobalContextCreateInGroup(nullptr, nullptr); JSObjectRef globalObject = JSContextGetGlobalObject(context); ASSERT(JSValueIsObject(context, globalObject)); JSObjectRef PingPongStackOverflowObject = JSObjectMake(context, PingPongStackOverflowObject_class(context), NULL); JSStringRef PingPongStackOverflowObjectString = JSStringCreateWithUTF8CString("PingPongStackOverflowObject"); JSObjectSetProperty(context, globalObject, PingPongStackOverflowObjectString, PingPongStackOverflowObject, kJSPropertyAttributeNone, NULL); JSStringRelease(PingPongStackOverflowObjectString); unsigned stackSize = 32 * KB; Options::maxPerThreadStackUsage() = stackSize + Options::reservedZoneSize(); exception = nullptr; scriptResult = JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception); if (!exception) { printf("FAIL: PingPongStackOverflowError not thrown in PingPongStackOverflow test\n"); failed = true; } else if (nativeRecursionCount) { printf("FAIL: Unbalanced native recursion count: %d in PingPongStackOverflow test\n", nativeRecursionCount); failed = true; } else { printf("PASS: PingPongStackOverflow test.\n"); } Options::reservedZoneSize() = origReservedZoneSize; Options::errorModeReservedZoneSize() = origErrorModeReservedZoneSize; Options::useLLInt() = origUseLLInt; Options::maxPerThreadStackUsage() = origMaxPerThreadStackUsage; return failed; }