PingPongStackOverflowTest.cpp
1 /* 2 * Copyright (C) 2015-2020 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "PingPongStackOverflowTest.h" 28 29 #include "InitializeThreading.h" 30 #include "JavaScript.h" 31 #include "Options.h" 32 #include <wtf/text/StringBuilder.h> 33 34 using JSC::Options; 35 36 static JSGlobalContextRef context = nullptr; 37 static int nativeRecursionCount = 0; 38 39 static bool PingPongStackOverflowObject_hasInstance(JSContextRef context, JSObjectRef constructor, JSValueRef possibleValue, JSValueRef* exception) 40 { 41 UNUSED_PARAM(context); 42 UNUSED_PARAM(constructor); 43 44 JSStringRef hasInstanceName = JSStringCreateWithUTF8CString("hasInstance"); 45 JSValueRef hasInstance = JSObjectGetProperty(context, constructor, hasInstanceName, exception); 46 JSStringRelease(hasInstanceName); 47 if (!hasInstance) 48 return false; 49 50 int countAtEntry = nativeRecursionCount++; 51 52 JSValueRef result = nullptr; 53 if (nativeRecursionCount < 100) { 54 JSObjectRef function = JSValueToObject(context, hasInstance, exception); 55 result = JSObjectCallAsFunction(context, function, constructor, 1, &possibleValue, exception); 56 } else { 57 StringBuilder builder; 58 builder.appendLiteral("dummy.valueOf([0]"); 59 for (int i = 1; i < 35000; i++) { 60 builder.appendLiteral(", ["); 61 builder.appendNumber(i); 62 builder.appendLiteral("]"); 63 } 64 builder.appendLiteral(");"); 65 66 JSStringRef script = JSStringCreateWithUTF8CString(builder.toString().utf8().data()); 67 result = JSEvaluateScript(context, script, nullptr, nullptr, 1, exception); 68 JSStringRelease(script); 69 } 70 71 --nativeRecursionCount; 72 if (nativeRecursionCount != countAtEntry) 73 printf(" ERROR: PingPongStackOverflow test saw a recursion count mismatch\n"); 74 75 return result && JSValueToBoolean(context, result); 76 } 77 78 JSClassDefinition PingPongStackOverflowObject_definition = { 79 0, 80 kJSClassAttributeNone, 81 82 "PingPongStackOverflowObject", 83 nullptr, 84 85 nullptr, 86 nullptr, 87 88 nullptr, 89 nullptr, 90 nullptr, 91 nullptr, 92 nullptr, 93 nullptr, 94 nullptr, 95 nullptr, 96 nullptr, 97 PingPongStackOverflowObject_hasInstance, 98 nullptr, 99 }; 100 101 static JSClassRef PingPongStackOverflowObject_class(JSContextRef context) 102 { 103 UNUSED_PARAM(context); 104 105 static JSClassRef jsClass; 106 if (!jsClass) 107 jsClass = JSClassCreate(&PingPongStackOverflowObject_definition); 108 109 return jsClass; 110 } 111 112 // This tests tests a stack overflow on VM reentry into a JS function from a native function 113 // after ping-pong'ing back and forth between JS and native functions multiple times. 114 // This test should not hang or crash. 115 int testPingPongStackOverflow() 116 { 117 bool failed = false; 118 119 JSC::initialize(); 120 121 auto origSoftReservedZoneSize = Options::softReservedZoneSize(); 122 auto origReservedZoneSize = Options::reservedZoneSize(); 123 auto origUseLLInt = Options::useLLInt(); 124 auto origMaxPerThreadStackUsage = Options::maxPerThreadStackUsage(); 125 126 Options::softReservedZoneSize() = 128 * KB; 127 Options::reservedZoneSize() = 64 * KB; 128 #if ENABLE(JIT) 129 // Normally, we want to disable the LLINT to force the use of JITted code which is necessary for 130 // reproducing the regression in https://bugs.webkit.org/show_bug.cgi?id=148749. However, we only 131 // want to do this if the LLINT isn't the only available execution engine. 132 Options::useLLInt() = false; 133 #endif 134 135 const char* scriptString = 136 "var count = 0;" \ 137 "PingPongStackOverflowObject.hasInstance = function f() {" \ 138 " return (undefined instanceof PingPongStackOverflowObject);" \ 139 "};" \ 140 "PingPongStackOverflowObject.__proto__ = undefined;" \ 141 "undefined instanceof PingPongStackOverflowObject;"; 142 143 JSValueRef exception = nullptr; 144 JSStringRef script = JSStringCreateWithUTF8CString(scriptString); 145 146 nativeRecursionCount = 0; 147 context = JSGlobalContextCreateInGroup(nullptr, nullptr); 148 149 JSObjectRef globalObject = JSContextGetGlobalObject(context); 150 ASSERT(JSValueIsObject(context, globalObject)); 151 152 JSObjectRef PingPongStackOverflowObject = JSObjectMake(context, PingPongStackOverflowObject_class(context), nullptr); 153 JSStringRef PingPongStackOverflowObjectString = JSStringCreateWithUTF8CString("PingPongStackOverflowObject"); 154 JSObjectSetProperty(context, globalObject, PingPongStackOverflowObjectString, PingPongStackOverflowObject, kJSPropertyAttributeNone, nullptr); 155 JSStringRelease(PingPongStackOverflowObjectString); 156 157 unsigned stackSize = 32 * KB; 158 Options::maxPerThreadStackUsage() = stackSize + Options::softReservedZoneSize(); 159 160 exception = nullptr; 161 JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception); 162 163 JSGlobalContextRelease(context); 164 context = nullptr; 165 JSStringRelease(script); 166 167 if (!exception) { 168 printf("FAIL: PingPongStackOverflowError not thrown in PingPongStackOverflow test\n"); 169 failed = true; 170 } else if (nativeRecursionCount) { 171 printf("FAIL: Unbalanced native recursion count: %d in PingPongStackOverflow test\n", nativeRecursionCount); 172 failed = true; 173 } else { 174 printf("PASS: PingPongStackOverflow test.\n"); 175 } 176 177 Options::softReservedZoneSize() = origSoftReservedZoneSize; 178 Options::reservedZoneSize() = origReservedZoneSize; 179 Options::useLLInt() = origUseLLInt; 180 Options::maxPerThreadStackUsage() = origMaxPerThreadStackUsage; 181 182 return failed; 183 }