| /* |
| * Copyright (C) 2015-2020 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 "JavaScript.h" |
| #include "Options.h" |
| #include <wtf/text/StringBuilder.h> |
| |
| 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 = nullptr; |
| 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(", [", i, ']'); |
| builder.append(");"); |
| JSStringRef script = JSStringCreateWithUTF8CString(builder.toString().utf8().data()); |
| result = JSEvaluateScript(context, script, nullptr, nullptr, 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", |
| nullptr, |
| |
| nullptr, |
| nullptr, |
| |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| nullptr, |
| PingPongStackOverflowObject_hasInstance, |
| nullptr, |
| }; |
| |
| 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::initialize(); |
| |
| auto origSoftReservedZoneSize = Options::softReservedZoneSize(); |
| auto origReservedZoneSize = Options::reservedZoneSize(); |
| auto origUseLLInt = Options::useLLInt(); |
| auto origMaxPerThreadStackUsage = Options::maxPerThreadStackUsage(); |
| |
| Options::softReservedZoneSize() = 128 * KB; |
| Options::reservedZoneSize() = 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 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), nullptr); |
| JSStringRef PingPongStackOverflowObjectString = JSStringCreateWithUTF8CString("PingPongStackOverflowObject"); |
| JSObjectSetProperty(context, globalObject, PingPongStackOverflowObjectString, PingPongStackOverflowObject, kJSPropertyAttributeNone, nullptr); |
| JSStringRelease(PingPongStackOverflowObjectString); |
| |
| unsigned stackSize = 32 * KB; |
| Options::maxPerThreadStackUsage() = stackSize + Options::softReservedZoneSize(); |
| |
| exception = nullptr; |
| JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception); |
| |
| JSGlobalContextRelease(context); |
| context = nullptr; |
| JSStringRelease(script); |
| |
| 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::softReservedZoneSize() = origSoftReservedZoneSize; |
| Options::reservedZoneSize() = origReservedZoneSize; |
| Options::useLLInt() = origUseLLInt; |
| Options::maxPerThreadStackUsage() = origMaxPerThreadStackUsage; |
| |
| return failed; |
| } |