1/*
2 * Copyright (C) 2015 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 "JSContextRefPrivate.h"
31#include "JavaScript.h"
32#include "Options.h"
33#include <wtf/text/StringBuilder.h>
34
35using JSC::Options;
36
37static JSGlobalContextRef context = nullptr;
38static int nativeRecursionCount = 0;
39
40static bool PingPongStackOverflowObject_hasInstance(JSContextRef context, JSObjectRef constructor, JSValueRef possibleValue, JSValueRef* exception)
41{
42 UNUSED_PARAM(context);
43 UNUSED_PARAM(constructor);
44
45 JSStringRef hasInstanceName = JSStringCreateWithUTF8CString("hasInstance");
46 JSValueRef hasInstance = JSObjectGetProperty(context, constructor, hasInstanceName, exception);
47 JSStringRelease(hasInstanceName);
48 if (!hasInstance)
49 return false;
50
51 int countAtEntry = nativeRecursionCount++;
52
53 JSValueRef result = 0;
54 if (nativeRecursionCount < 100) {
55 JSObjectRef function = JSValueToObject(context, hasInstance, exception);
56 result = JSObjectCallAsFunction(context, function, constructor, 1, &possibleValue, exception);
57 } else {
58 StringBuilder builder;
59 builder.appendLiteral("dummy.valueOf([0]");
60 for (int i = 1; i < 35000; i++) {
61 builder.appendLiteral(", [");
62 builder.appendNumber(i);
63 builder.appendLiteral("]");
64 }
65 builder.appendLiteral(");");
66
67 JSStringRef script = JSStringCreateWithUTF8CString(builder.toString().utf8().data());
68 result = JSEvaluateScript(context, script, NULL, NULL, 1, exception);
69 JSStringRelease(script);
70 }
71
72 --nativeRecursionCount;
73 if (nativeRecursionCount != countAtEntry)
74 printf(" ERROR: PingPongStackOverflow test saw a recursion count mismatch\n");
75
76 return result && JSValueToBoolean(context, result);
77}
78
79JSClassDefinition PingPongStackOverflowObject_definition = {
80 0,
81 kJSClassAttributeNone,
82
83 "PingPongStackOverflowObject",
84 NULL,
85
86 NULL,
87 NULL,
88
89 NULL,
90 NULL,
91 NULL,
92 NULL,
93 NULL,
94 NULL,
95 NULL,
96 NULL,
97 NULL,
98 PingPongStackOverflowObject_hasInstance,
99 NULL,
100};
101
102static JSClassRef PingPongStackOverflowObject_class(JSContextRef context)
103{
104 UNUSED_PARAM(context);
105
106 static JSClassRef jsClass;
107 if (!jsClass)
108 jsClass = JSClassCreate(&PingPongStackOverflowObject_definition);
109
110 return jsClass;
111}
112
113// This tests tests a stack overflow on VM reentry into a JS function from a native function
114// after ping-pong'ing back and forth between JS and native functions multiple times.
115// This test should not hang or crash.
116int testPingPongStackOverflow()
117{
118 bool failed = false;
119
120 JSC::initializeThreading();
121 Options::initialize(); // Ensure options is initialized first.
122
123 auto origSoftReservedZoneSize = Options::softReservedZoneSize();
124 auto origReservedZoneSize = Options::reservedZoneSize();
125 auto origUseLLInt = Options::useLLInt();
126 auto origMaxPerThreadStackUsage = Options::maxPerThreadStackUsage();
127
128 Options::softReservedZoneSize() = 128 * KB;
129 Options::reservedZoneSize() = 64 * KB;
130#if ENABLE(JIT)
131 // Normally, we want to disable the LLINT to force the use of JITted code which is necessary for
132 // reproducing the regression in https://bugs.webkit.org/show_bug.cgi?id=148749. However, we only
133 // want to do this if the LLINT isn't the only available execution engine.
134 Options::useLLInt() = false;
135#endif
136
137 const char* scriptString =
138 "var count = 0;" \
139 "PingPongStackOverflowObject.hasInstance = function f() {" \
140 " return (undefined instanceof PingPongStackOverflowObject);" \
141 "};" \
142 "PingPongStackOverflowObject.__proto__ = undefined;" \
143 "undefined instanceof PingPongStackOverflowObject;";
144
145 JSValueRef exception = nullptr;
146 JSStringRef script = JSStringCreateWithUTF8CString(scriptString);
147
148 nativeRecursionCount = 0;
149 context = JSGlobalContextCreateInGroup(nullptr, nullptr);
150
151 JSObjectRef globalObject = JSContextGetGlobalObject(context);
152 ASSERT(JSValueIsObject(context, globalObject));
153
154 JSObjectRef PingPongStackOverflowObject = JSObjectMake(context, PingPongStackOverflowObject_class(context), NULL);
155 JSStringRef PingPongStackOverflowObjectString = JSStringCreateWithUTF8CString("PingPongStackOverflowObject");
156 JSObjectSetProperty(context, globalObject, PingPongStackOverflowObjectString, PingPongStackOverflowObject, kJSPropertyAttributeNone, NULL);
157 JSStringRelease(PingPongStackOverflowObjectString);
158
159 unsigned stackSize = 32 * KB;
160 Options::maxPerThreadStackUsage() = stackSize + Options::softReservedZoneSize();
161
162 exception = nullptr;
163 JSEvaluateScript(context, script, nullptr, nullptr, 1, &exception);
164
165 JSGlobalContextRelease(context);
166 context = nullptr;
167 JSStringRelease(script);
168
169 if (!exception) {
170 printf("FAIL: PingPongStackOverflowError not thrown in PingPongStackOverflow test\n");
171 failed = true;
172 } else if (nativeRecursionCount) {
173 printf("FAIL: Unbalanced native recursion count: %d in PingPongStackOverflow test\n", nativeRecursionCount);
174 failed = true;
175 } else {
176 printf("PASS: PingPongStackOverflow test.\n");
177 }
178
179 Options::softReservedZoneSize() = origSoftReservedZoneSize;
180 Options::reservedZoneSize() = origReservedZoneSize;
181 Options::useLLInt() = origUseLLInt;
182 Options::maxPerThreadStackUsage() = origMaxPerThreadStackUsage;
183
184 return failed;
185}
186