/ API / tests / PingPongStackOverflowTest.cpp
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  }