1/*
2 * Copyright (C) 2017-2019 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
28#include "APICast.h"
29#include "JSCJSValueInlines.h"
30#include "JSGlobalObjectInlines.h"
31#include "JSObject.h"
32
33#include <JavaScriptCore/JSContextRefPrivate.h>
34#include <JavaScriptCore/JSObjectRefPrivate.h>
35#include <JavaScriptCore/JavaScript.h>
36#include <wtf/DataLog.h>
37#include <wtf/Expected.h>
38#include <wtf/Noncopyable.h>
39#include <wtf/NumberOfCores.h>
40#include <wtf/Vector.h>
41#include <wtf/text/StringCommon.h>
42
43extern "C" void configureJSCForTesting();
44extern "C" int testCAPIViaCpp(const char* filter);
45
46class APIString {
47 WTF_MAKE_NONCOPYABLE(APIString);
48public:
49
50 APIString(const char* string)
51 : m_string(JSStringCreateWithUTF8CString(string))
52 {
53 }
54
55 ~APIString()
56 {
57 JSStringRelease(m_string);
58 }
59
60 operator JSStringRef() { return m_string; }
61
62private:
63 JSStringRef m_string;
64};
65
66class APIContext {
67 WTF_MAKE_NONCOPYABLE(APIContext);
68public:
69
70 APIContext()
71 : m_context(JSGlobalContextCreate(nullptr))
72 {
73 APIString print("print");
74 JSObjectRef printFunction = JSObjectMakeFunctionWithCallback(m_context, print, [] (JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) {
75
76 JSC::JSGlobalObject* globalObject = toJS(ctx);
77 for (unsigned i = 0; i < argumentCount; i++)
78 dataLog(toJS(globalObject, arguments[i]));
79 dataLogLn();
80 return JSValueMakeUndefined(ctx);
81 });
82
83 JSObjectSetProperty(m_context, JSContextGetGlobalObject(m_context), print, printFunction, kJSPropertyAttributeNone, nullptr);
84 }
85
86 ~APIContext()
87 {
88 JSGlobalContextRelease(m_context);
89 }
90
91 operator JSGlobalContextRef() { return m_context; }
92 operator JSC::JSGlobalObject*() { return toJS(m_context); }
93
94private:
95 JSGlobalContextRef m_context;
96};
97
98template<typename T>
99class APIVector : protected Vector<T> {
100 using Base = Vector<T>;
101public:
102 APIVector(APIContext& context)
103 : Base()
104 , m_context(context)
105 {
106 }
107
108 ~APIVector()
109 {
110 for (auto& value : *this)
111 JSValueUnprotect(m_context, value);
112 }
113
114 using Vector<T>::operator[];
115 using Vector<T>::size;
116 using Vector<T>::begin;
117 using Vector<T>::end;
118 using typename Vector<T>::iterator;
119
120 void append(T value)
121 {
122 JSValueProtect(m_context, value);
123 Base::append(WTFMove(value));
124 }
125
126private:
127 APIContext& m_context;
128};
129
130class TestAPI {
131public:
132 int run(const char* filter);
133
134 void basicSymbol();
135 void symbolsTypeof();
136 void symbolsDescription();
137 void symbolsGetPropertyForKey();
138 void symbolsSetPropertyForKey();
139 void symbolsHasPropertyForKey();
140 void symbolsDeletePropertyForKey();
141 void promiseResolveTrue();
142 void promiseRejectTrue();
143 void promiseUnhandledRejection();
144 void promiseUnhandledRejectionFromUnhandledRejectionCallback();
145 void promiseEarlyHandledRejections();
146 void topCallFrameAccess();
147
148 int failed() const { return m_failed; }
149
150private:
151
152 template<typename... Strings>
153 bool check(bool condition, Strings... message);
154
155 template<typename JSFunctor, typename APIFunctor>
156 void checkJSAndAPIMatch(const JSFunctor&, const APIFunctor&, const char* description);
157
158 // Helper methods.
159 using ScriptResult = Expected<JSValueRef, JSValueRef>;
160 ScriptResult evaluateScript(const char* script, JSObjectRef thisObject = nullptr);
161 template<typename... ArgumentTypes>
162 ScriptResult callFunction(const char* functionSource, ArgumentTypes... arguments);
163 template<typename... ArgumentTypes>
164 bool functionReturnsTrue(const char* functionSource, ArgumentTypes... arguments);
165
166 // Ways to make sets of interesting things.
167 APIVector<JSObjectRef> interestingObjects();
168 APIVector<JSValueRef> interestingKeys();
169
170 int m_failed { 0 };
171 APIContext context;
172};
173
174TestAPI::ScriptResult TestAPI::evaluateScript(const char* script, JSObjectRef thisObject)
175{
176 APIString scriptAPIString(script);
177 JSValueRef exception = nullptr;
178
179 JSValueRef result = JSEvaluateScript(context, scriptAPIString, thisObject, nullptr, 0, &exception);
180 if (exception)
181 return Unexpected<JSValueRef>(exception);
182 return ScriptResult(result);
183}
184
185template<typename... ArgumentTypes>
186TestAPI::ScriptResult TestAPI::callFunction(const char* functionSource, ArgumentTypes... arguments)
187{
188 JSValueRef function;
189 {
190 ScriptResult functionResult = evaluateScript(functionSource);
191 if (!functionResult)
192 return functionResult;
193 function = functionResult.value();
194 }
195
196 JSValueRef exception = nullptr;
197 if (JSObjectRef functionObject = JSValueToObject(context, function, &exception)) {
198 JSValueRef args[sizeof...(arguments)] { arguments... };
199 JSValueRef result = JSObjectCallAsFunction(context, functionObject, functionObject, sizeof...(arguments), args, &exception);
200 if (!exception)
201 return ScriptResult(result);
202 }
203
204 RELEASE_ASSERT(exception);
205 return Unexpected<JSValueRef>(exception);
206}
207
208#if COMPILER(MSVC)
209template<>
210TestAPI::ScriptResult TestAPI::callFunction(const char* functionSource)
211{
212 JSValueRef function;
213 {
214 ScriptResult functionResult = evaluateScript(functionSource);
215 if (!functionResult)
216 return functionResult;
217 function = functionResult.value();
218 }
219
220 JSValueRef exception = nullptr;
221 if (JSObjectRef functionObject = JSValueToObject(context, function, &exception)) {
222 JSValueRef result = JSObjectCallAsFunction(context, functionObject, functionObject, 0, nullptr, &exception);
223 if (!exception)
224 return ScriptResult(result);
225 }
226
227 RELEASE_ASSERT(exception);
228 return Unexpected<JSValueRef>(exception);
229}
230#endif
231
232template<typename... ArgumentTypes>
233bool TestAPI::functionReturnsTrue(const char* functionSource, ArgumentTypes... arguments)
234{
235 JSValueRef trueValue = JSValueMakeBoolean(context, true);
236 ScriptResult result = callFunction(functionSource, arguments...);
237 if (!result)
238 return false;
239 return JSValueIsStrictEqual(context, trueValue, result.value());
240}
241
242template<typename... Strings>
243bool TestAPI::check(bool condition, Strings... messages)
244{
245 if (!condition) {
246 dataLogLn(messages..., ": FAILED");
247 m_failed++;
248 } else
249 dataLogLn(messages..., ": PASSED");
250
251 return condition;
252}
253
254template<typename JSFunctor, typename APIFunctor>
255void TestAPI::checkJSAndAPIMatch(const JSFunctor& jsFunctor, const APIFunctor& apiFunctor, const char* description)
256{
257 JSValueRef exception = nullptr;
258 JSValueRef result = apiFunctor(&exception);
259 ScriptResult jsResult = jsFunctor();
260 if (!jsResult) {
261 check(exception, "JS and API calls should both throw an exception while ", description);
262 check(functionReturnsTrue("(function(a, b) { return a.constructor === b.constructor; })", exception, jsResult.error()), "JS and API calls should both throw the same exception while ", description);
263 } else {
264 check(!exception, "JS and API calls should both not throw an exception while ", description);
265 check(JSValueIsStrictEqual(context, result, jsResult.value()), "JS result and API calls should return the same value while ", description);
266 }
267}
268
269APIVector<JSObjectRef> TestAPI::interestingObjects()
270{
271 APIVector<JSObjectRef> result(context);
272 JSObjectRef array = JSValueToObject(context, evaluateScript(
273 "[{}, [], { [Symbol.iterator]: 1 }, new Date(), new String('str'), new Map(), new Set(), new WeakMap(), new WeakSet(), new Error(), new Number(42), new Boolean(), { get length() { throw new Error(); } }];").value(), nullptr);
274
275 APIString lengthString("length");
276 unsigned length = JSValueToNumber(context, JSObjectGetProperty(context, array, lengthString, nullptr), nullptr);
277 for (unsigned i = 0; i < length; i++) {
278 JSObjectRef object = JSValueToObject(context, JSObjectGetPropertyAtIndex(context, array, i, nullptr), nullptr);
279 ASSERT(object);
280 result.append(object);
281 }
282
283 return result;
284}
285
286APIVector<JSValueRef> TestAPI::interestingKeys()
287{
288 APIVector<JSValueRef> result(context);
289 JSObjectRef array = JSValueToObject(context, evaluateScript("[{}, [], 1, Symbol.iterator, 'length']").value(), nullptr);
290
291 APIString lengthString("length");
292 unsigned length = JSValueToNumber(context, JSObjectGetProperty(context, array, lengthString, nullptr), nullptr);
293 for (unsigned i = 0; i < length; i++) {
294 JSValueRef value = JSObjectGetPropertyAtIndex(context, array, i, nullptr);
295 ASSERT(value);
296 result.append(value);
297 }
298
299 return result;
300}
301
302static const char* isSymbolFunction = "(function isSymbol(symbol) { return typeof(symbol) === 'symbol'; })";
303static const char* getSymbolDescription = "(function getSymbolDescription(symbol) { return symbol.description; })";
304static const char* getFunction = "(function get(object, key) { return object[key]; })";
305static const char* setFunction = "(function set(object, key, value) { object[key] = value; })";
306
307void TestAPI::basicSymbol()
308{
309 // Can't call Symbol as a constructor since it's not subclassable.
310 auto result = evaluateScript("Symbol('dope');");
311 check(JSValueGetType(context, result.value()) == kJSTypeSymbol, "dope get type is a symbol");
312 check(JSValueIsSymbol(context, result.value()), "dope is a symbol");
313}
314
315void TestAPI::symbolsTypeof()
316{
317 {
318 JSValueRef symbol = JSValueMakeSymbol(context, nullptr);
319 check(functionReturnsTrue(isSymbolFunction, symbol), "JSValueMakeSymbol makes a symbol value");
320 }
321 {
322 APIString description("dope");
323 JSValueRef symbol = JSValueMakeSymbol(context, description);
324 check(functionReturnsTrue(isSymbolFunction, symbol), "JSValueMakeSymbol makes a symbol value");
325 }
326}
327
328void TestAPI::symbolsDescription()
329{
330 {
331 JSValueRef symbol = JSValueMakeSymbol(context, nullptr);
332 auto result = callFunction(getSymbolDescription, symbol);
333 check(JSValueIsStrictEqual(context, result.value(), JSValueMakeUndefined(context)), "JSValueMakeSymbol with nullptr description produces a symbol value without description");
334 }
335 {
336 APIString description("dope");
337 JSValueRef symbol = JSValueMakeSymbol(context, description);
338 auto result = callFunction(getSymbolDescription, symbol);
339 check(JSValueIsStrictEqual(context, result.value(), JSValueMakeString(context, description)), "JSValueMakeSymbol with description string produces a symbol value with description");
340 }
341}
342
343void TestAPI::symbolsGetPropertyForKey()
344{
345 auto objects = interestingObjects();
346 auto keys = interestingKeys();
347
348 for (auto& object : objects) {
349 dataLogLn("\nnext object: ", toJS(context, object));
350 for (auto& key : keys) {
351 dataLogLn("Using key: ", toJS(context, key));
352 checkJSAndAPIMatch(
353 [&] {
354 return callFunction(getFunction, object, key);
355 }, [&] (JSValueRef* exception) {
356 return JSObjectGetPropertyForKey(context, object, key, exception);
357 }, "checking get property keys");
358 }
359 }
360}
361
362void TestAPI::symbolsSetPropertyForKey()
363{
364 auto jsObjects = interestingObjects();
365 auto apiObjects = interestingObjects();
366 auto keys = interestingKeys();
367
368 JSValueRef theAnswer = JSValueMakeNumber(context, 42);
369 for (size_t i = 0; i < jsObjects.size(); i++) {
370 for (auto& key : keys) {
371 JSObjectRef jsObject = jsObjects[i];
372 JSObjectRef apiObject = apiObjects[i];
373 checkJSAndAPIMatch(
374 [&] {
375 return callFunction(setFunction, jsObject, key, theAnswer);
376 } , [&] (JSValueRef* exception) {
377 JSObjectSetPropertyForKey(context, apiObject, key, theAnswer, kJSPropertyAttributeNone, exception);
378 return JSValueMakeUndefined(context);
379 }, "setting property keys to the answer");
380 // Check get is the same on API object.
381 checkJSAndAPIMatch(
382 [&] {
383 return callFunction(getFunction, apiObject, key);
384 }, [&] (JSValueRef* exception) {
385 return JSObjectGetPropertyForKey(context, apiObject, key, exception);
386 }, "getting property keys from API objects");
387 // Check get is the same on respective objects.
388 checkJSAndAPIMatch(
389 [&] {
390 return callFunction(getFunction, jsObject, key);
391 }, [&] (JSValueRef* exception) {
392 return JSObjectGetPropertyForKey(context, apiObject, key, exception);
393 }, "getting property keys from respective objects");
394 }
395 }
396}
397
398void TestAPI::symbolsHasPropertyForKey()
399{
400 const char* hasFunction = "(function has(object, key) { return key in object; })";
401 auto objects = interestingObjects();
402 auto keys = interestingKeys();
403
404 JSValueRef theAnswer = JSValueMakeNumber(context, 42);
405 for (auto& object : objects) {
406 dataLogLn("\nNext object: ", toJS(context, object));
407 for (auto& key : keys) {
408 dataLogLn("Using key: ", toJS(context, key));
409 checkJSAndAPIMatch(
410 [&] {
411 return callFunction(hasFunction, object, key);
412 }, [&] (JSValueRef* exception) {
413 return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
414 }, "checking has property keys unset");
415
416 check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
417
418 checkJSAndAPIMatch(
419 [&] {
420 return callFunction(hasFunction, object, key);
421 }, [&] (JSValueRef* exception) {
422 return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
423 }, "checking has property keys set");
424 }
425 }
426}
427
428
429void TestAPI::symbolsDeletePropertyForKey()
430{
431 const char* deleteFunction = "(function del(object, key) { return delete object[key]; })";
432 auto objects = interestingObjects();
433 auto keys = interestingKeys();
434
435 JSValueRef theAnswer = JSValueMakeNumber(context, 42);
436 for (auto& object : objects) {
437 dataLogLn("\nNext object: ", toJS(context, object));
438 for (auto& key : keys) {
439 dataLogLn("Using key: ", toJS(context, key));
440 checkJSAndAPIMatch(
441 [&] {
442 return callFunction(deleteFunction, object, key);
443 }, [&] (JSValueRef* exception) {
444 return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
445 }, "checking has property keys unset");
446
447 check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
448
449 checkJSAndAPIMatch(
450 [&] {
451 return callFunction(deleteFunction, object, key);
452 }, [&] (JSValueRef* exception) {
453 return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
454 }, "checking has property keys set");
455 }
456 }
457}
458
459void TestAPI::promiseResolveTrue()
460{
461 JSObjectRef resolve;
462 JSObjectRef reject;
463 JSValueRef exception = nullptr;
464 JSObjectRef promise = JSObjectMakeDeferredPromise(context, &resolve, &reject, &exception);
465 check(!exception, "No exception should be thrown creating a deferred promise");
466
467 // Ugh, we don't have any C API that takes blocks... so we do this hack to capture the runner.
468 static TestAPI* tester = this;
469 static bool passedTrueCalled = false;
470
471 APIString trueString("passedTrue");
472 auto passedTrue = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) -> JSValueRef {
473 tester->check(argumentCount && JSValueIsStrictEqual(ctx, arguments[0], JSValueMakeBoolean(ctx, true)), "function should have been called back with true");
474 passedTrueCalled = true;
475 return JSValueMakeUndefined(ctx);
476 };
477
478 APIString thenString("then");
479 JSValueRef thenFunction = JSObjectGetProperty(context, promise, thenString, &exception);
480 check(!exception && thenFunction && JSValueIsObject(context, thenFunction), "Promise should have a then object property");
481
482 JSValueRef passedTrueFunction = JSObjectMakeFunctionWithCallback(context, trueString, passedTrue);
483 JSObjectCallAsFunction(context, const_cast<JSObjectRef>(thenFunction), promise, 1, &passedTrueFunction, &exception);
484 check(!exception, "No exception should be thrown setting up callback");
485
486 auto trueValue = JSValueMakeBoolean(context, true);
487 JSObjectCallAsFunction(context, resolve, resolve, 1, &trueValue, &exception);
488 check(!exception, "No exception should be thrown resolving promise");
489 check(passedTrueCalled, "then response function should have been called.");
490}
491
492void TestAPI::promiseRejectTrue()
493{
494 JSObjectRef resolve;
495 JSObjectRef reject;
496 JSValueRef exception = nullptr;
497 JSObjectRef promise = JSObjectMakeDeferredPromise(context, &resolve, &reject, &exception);
498 check(!exception, "No exception should be thrown creating a deferred promise");
499
500 // Ugh, we don't have any C API that takes blocks... so we do this hack to capture the runner.
501 static TestAPI* tester = this;
502 static bool passedTrueCalled = false;
503
504 APIString trueString("passedTrue");
505 auto passedTrue = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) -> JSValueRef {
506 tester->check(argumentCount && JSValueIsStrictEqual(ctx, arguments[0], JSValueMakeBoolean(ctx, true)), "function should have been called back with true");
507 passedTrueCalled = true;
508 return JSValueMakeUndefined(ctx);
509 };
510
511 APIString catchString("catch");
512 JSValueRef catchFunction = JSObjectGetProperty(context, promise, catchString, &exception);
513 check(!exception && catchFunction && JSValueIsObject(context, catchFunction), "Promise should have a catch object property");
514
515 JSValueRef passedTrueFunction = JSObjectMakeFunctionWithCallback(context, trueString, passedTrue);
516 JSObjectCallAsFunction(context, const_cast<JSObjectRef>(catchFunction), promise, 1, &passedTrueFunction, &exception);
517 check(!exception, "No exception should be thrown setting up callback");
518
519 auto trueValue = JSValueMakeBoolean(context, true);
520 JSObjectCallAsFunction(context, reject, reject, 1, &trueValue, &exception);
521 check(!exception, "No exception should be thrown rejecting promise");
522 check(passedTrueCalled, "catch response function should have been called.");
523}
524
525void TestAPI::promiseUnhandledRejection()
526{
527 JSObjectRef reject;
528 JSValueRef exception = nullptr;
529 static auto promise = JSObjectMakeDeferredPromise(context, nullptr, &reject, &exception);
530 check(!exception, "creating a (reject-only) deferred promise should not throw");
531 static auto reason = JSValueMakeString(context, APIString("reason"));
532
533 static TestAPI* tester = this;
534 static bool callbackCalled = false;
535 auto callback = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) -> JSValueRef {
536 tester->check(argumentCount && JSValueIsStrictEqual(ctx, arguments[0], promise), "callback should receive rejected promise as first argument");
537 tester->check(argumentCount > 1 && JSValueIsStrictEqual(ctx, arguments[1], reason), "callback should receive rejection reason as second argument");
538 tester->check(argumentCount == 2, "callback should not receive a third argument");
539 callbackCalled = true;
540 return JSValueMakeUndefined(ctx);
541 };
542 auto callbackFunction = JSObjectMakeFunctionWithCallback(context, APIString("callback"), callback);
543
544 JSGlobalContextSetUnhandledRejectionCallback(context, callbackFunction, &exception);
545 check(!exception, "setting unhandled rejection callback should not throw");
546
547 JSObjectCallAsFunction(context, reject, reject, 1, &reason, &exception);
548 check(!exception && callbackCalled, "unhandled rejection callback should be called upon unhandled rejection");
549}
550
551void TestAPI::promiseUnhandledRejectionFromUnhandledRejectionCallback()
552{
553 static JSObjectRef reject;
554 static JSValueRef exception = nullptr;
555 JSObjectMakeDeferredPromise(context, nullptr, &reject, &exception);
556 check(!exception, "creating a (reject-only) deferred promise should not throw");
557
558 static auto callbackCallCount = 0;
559 auto callback = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*) -> JSValueRef {
560 if (!callbackCallCount)
561 JSObjectCallAsFunction(ctx, reject, reject, 0, nullptr, &exception);
562 callbackCallCount++;
563 return JSValueMakeUndefined(ctx);
564 };
565 auto callbackFunction = JSObjectMakeFunctionWithCallback(context, APIString("callback"), callback);
566
567 JSGlobalContextSetUnhandledRejectionCallback(context, callbackFunction, &exception);
568 check(!exception, "setting unhandled rejection callback should not throw");
569
570 callFunction("(function () { Promise.reject(); })");
571 check(!exception && callbackCallCount == 2, "unhandled rejection from unhandled rejection callback should also trigger the callback");
572}
573
574void TestAPI::promiseEarlyHandledRejections()
575{
576 JSValueRef exception = nullptr;
577
578 static bool callbackCalled = false;
579 auto callback = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*) -> JSValueRef {
580 callbackCalled = true;
581 return JSValueMakeUndefined(ctx);
582 };
583 auto callbackFunction = JSObjectMakeFunctionWithCallback(context, APIString("callback"), callback);
584
585 JSGlobalContextSetUnhandledRejectionCallback(context, callbackFunction, &exception);
586 check(!exception, "setting unhandled rejection callback should not throw");
587
588 callFunction("(function () { const p = Promise.reject(); p.catch(() => {}); })");
589 check(!callbackCalled, "unhandled rejection callback should not be called for synchronous early-handled rejection");
590
591 callFunction("(function () { const p = Promise.reject(); Promise.resolve().then(() => { p.catch(() => {}); }); })");
592 check(!callbackCalled, "unhandled rejection callback should not be called for asynchronous early-handled rejection");
593}
594
595void TestAPI::topCallFrameAccess()
596{
597 {
598 JSObjectRef function = JSValueToObject(context, evaluateScript("(function () { })").value(), nullptr);
599 APIString argumentsString("arguments");
600 auto arguments = JSObjectGetProperty(context, function, argumentsString, nullptr);
601 check(JSValueIsNull(context, arguments), "vm.topCallFrame access from C++ world should use nullptr internally for arguments");
602 }
603 {
604 JSObjectRef arguments = JSValueToObject(context, evaluateScript("(function ok(v) { return ok.arguments; })(42)").value(), nullptr);
605 check(!JSValueIsNull(context, arguments), "vm.topCallFrame is materialized and we found the caller function's arguments");
606 }
607 {
608 JSObjectRef function = JSValueToObject(context, evaluateScript("(function () { })").value(), nullptr);
609 APIString callerString("caller");
610 auto caller = JSObjectGetProperty(context, function, callerString, nullptr);
611 check(JSValueIsNull(context, caller), "vm.topCallFrame access from C++ world should use nullptr internally for caller");
612 }
613 {
614 JSObjectRef caller = JSValueToObject(context, evaluateScript("(function () { return (function ok(v) { return ok.caller; })(42); })()").value(), nullptr);
615 check(!JSValueIsNull(context, caller), "vm.topCallFrame is materialized and we found the caller function's caller");
616 }
617 {
618 JSObjectRef caller = JSValueToObject(context, evaluateScript("(function ok(v) { return ok.caller; })(42)").value(), nullptr);
619 check(JSValueIsNull(context, caller), "vm.topCallFrame is materialized and we found the caller function's caller, but the caller is global code");
620 }
621}
622
623void configureJSCForTesting()
624{
625 JSC::Config::configureForTesting();
626}
627
628#define RUN(test) do { \
629 if (!shouldRun(#test)) \
630 break; \
631 tasks.append( \
632 createSharedTask<void(TestAPI&)>( \
633 [&] (TestAPI& tester) { \
634 tester.test; \
635 dataLog(#test ": OK!\n"); \
636 })); \
637 } while (false)
638
639int testCAPIViaCpp(const char* filter)
640{
641 dataLogLn("Starting C-API tests in C++");
642
643 Deque<RefPtr<SharedTask<void(TestAPI&)>>> tasks;
644
645 auto shouldRun = [&] (const char* testName) -> bool {
646 return !filter || WTF::findIgnoringASCIICaseWithoutLength(testName, filter) != WTF::notFound;
647 };
648
649 RUN(topCallFrameAccess());
650 RUN(basicSymbol());
651 RUN(symbolsTypeof());
652 RUN(symbolsDescription());
653 RUN(symbolsGetPropertyForKey());
654 RUN(symbolsSetPropertyForKey());
655 RUN(symbolsHasPropertyForKey());
656 RUN(symbolsDeletePropertyForKey());
657 RUN(promiseResolveTrue());
658 RUN(promiseRejectTrue());
659 RUN(promiseUnhandledRejection());
660 RUN(promiseUnhandledRejectionFromUnhandledRejectionCallback());
661 RUN(promiseEarlyHandledRejections());
662
663 if (tasks.isEmpty()) {
664 dataLogLn("Filtered all tests: ERROR");
665 return 1;
666 }
667
668 Lock lock;
669
670 static Atomic<int> failed { 0 };
671 Vector<Ref<Thread>> threads;
672 for (unsigned i = filter ? 1 : WTF::numberOfProcessorCores(); i--;) {
673 threads.append(Thread::create(
674 "Testapi via C++ thread",
675 [&] () {
676 TestAPI tester;
677 for (;;) {
678 RefPtr<SharedTask<void(TestAPI&)>> task;
679 {
680 LockHolder locker(lock);
681 if (tasks.isEmpty())
682 break;
683 task = tasks.takeFirst();
684 }
685
686 task->run(tester);
687 }
688 failed.exchangeAdd(tester.failed());
689 }));
690 }
691
692 for (auto& thread : threads)
693 thread->waitForCompletion();
694
695 dataLogLn("C-API tests in C++ had ", failed.load(), " failures");
696 return failed.load();
697}
698