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 | |
35 | using JSC::Options; |
36 | |
37 | static JSGlobalContextRef context = nullptr; |
38 | static int nativeRecursionCount = 0; |
39 | |
40 | static 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 | |
79 | JSClassDefinition 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 | |
102 | static 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. |
116 | int 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 | |