Add Promise SPI
authorkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Sep 2018 21:16:20 +0000 (21:16 +0000)
committerkeith_miller@apple.com <keith_miller@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 21 Sep 2018 21:16:20 +0000 (21:16 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189809

Reviewed by Saam Barati.

Source/JavaScriptCore:

The Patch adds new SPI to create promises. It's mostly SPI because
I want to see how internal users react to it before we make it
public.

This patch adds a couple of new Obj-C SPI methods. The first
creates a new promise using the same API that JS does where the
user provides an executor callback. If an exception is raised
in/to that callback the promise is automagically rejected. The
other methods create a pre-resolved or rejected promise as this
appears to be a common way to initialize a promise.

I was also considering adding a second version of executor API
where it would catch specific Obj-C exceptions. This would work by
taking a Class paramter and checking isKindOfClass: on the
exception. I decided against this as nothing else in our API
handles Obj-C exceptions. I'm pretty sure the VM will end up in a
corrupt state if an Obj-C exception unwinds through JS frames.

This patch adds a new C function that will create a "deferred"
promise. A deferred promise is a style of creating promise/futures
where the resolve and reject functions are passed as outputs of a
function. I went with this style for the C SPI because we don't have
any concept of forwarding exceptions in the C API.

In order to make the C API work I refactored a bit of the promise code
so that we can call a static method on JSDeferredPromise and just get
the components without allocating an extra cell wrapper.

* API/JSContext.mm:
(+[JSContext currentCallee]):
* API/JSObjectRef.cpp:
(JSObjectMakeDeferredPromise):
* API/JSObjectRefPrivate.h:
* API/JSValue.mm:
(+[JSValue valueWithNewPromiseInContext:fromExecutor:]):
(+[JSValue valueWithNewPromiseResolvedWithResult:inContext:]):
(+[JSValue valueWithNewPromiseRejectedWithReason:inContext:]):
* API/JSValuePrivate.h: Added.
* API/JSVirtualMachine.mm:
* API/JSVirtualMachinePrivate.h:
* API/tests/testapi.c:
(main):
* API/tests/testapi.cpp:
(APIContext::operator JSC::ExecState*):
(TestAPI::failed const):
(TestAPI::check):
(TestAPI::basicSymbol):
(TestAPI::symbolsTypeof):
(TestAPI::symbolsGetPropertyForKey):
(TestAPI::symbolsSetPropertyForKey):
(TestAPI::symbolsHasPropertyForKey):
(TestAPI::symbolsDeletePropertyForKey):
(TestAPI::promiseResolveTrue):
(TestAPI::promiseRejectTrue):
(testCAPIViaCpp):
(TestAPI::run): Deleted.
* API/tests/testapi.mm:
(testObjectiveCAPIMain):
(promiseWithExecutor):
(promiseRejectOnJSException):
(promiseCreateResolved):
(promiseCreateRejected):
(parallelPromiseResolveTest):
(testObjectiveCAPI):
* JavaScriptCore.xcodeproj/project.pbxproj:
* runtime/JSInternalPromiseDeferred.cpp:
(JSC::JSInternalPromiseDeferred::create):
* runtime/JSPromise.h:
* runtime/JSPromiseConstructor.cpp:
(JSC::constructPromise):
* runtime/JSPromiseDeferred.cpp:
(JSC::JSPromiseDeferred::createDeferredData):
(JSC::JSPromiseDeferred::create):
(JSC::JSPromiseDeferred::finishCreation):
(JSC::newPromiseCapability): Deleted.
* runtime/JSPromiseDeferred.h:
(JSC::JSPromiseDeferred::promise const):
(JSC::JSPromiseDeferred::resolve const):
(JSC::JSPromiseDeferred::reject const):

Source/WTF:

Fix issue where creating a JSContextRef off the main thread before
creating initializing the main thread would cause an assertion
failure.

* wtf/MainThread.cpp:
(WTF::isMainThreadIfInitialized):
* wtf/MainThread.h:
* wtf/mac/MainThreadMac.mm:
(WTF::isMainThreadIfInitialized):
* wtf/text/cf/StringImplCF.cpp:
(WTF::StringImpl::createCFString):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@236359 268f45cc-cd09-0410-ab3c-d52691b4dbfc

22 files changed:
Source/JavaScriptCore/API/JSContext.mm
Source/JavaScriptCore/API/JSObjectRef.cpp
Source/JavaScriptCore/API/JSObjectRefPrivate.h
Source/JavaScriptCore/API/JSValue.mm
Source/JavaScriptCore/API/JSValuePrivate.h [new file with mode: 0644]
Source/JavaScriptCore/API/JSVirtualMachine.mm
Source/JavaScriptCore/API/JSVirtualMachinePrivate.h
Source/JavaScriptCore/API/tests/testapi.c
Source/JavaScriptCore/API/tests/testapi.cpp
Source/JavaScriptCore/API/tests/testapi.mm
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/runtime/JSInternalPromiseDeferred.cpp
Source/JavaScriptCore/runtime/JSPromise.h
Source/JavaScriptCore/runtime/JSPromiseConstructor.cpp
Source/JavaScriptCore/runtime/JSPromiseDeferred.cpp
Source/JavaScriptCore/runtime/JSPromiseDeferred.h
Source/WTF/ChangeLog
Source/WTF/wtf/MainThread.cpp
Source/WTF/wtf/MainThread.h
Source/WTF/wtf/mac/MainThreadMac.mm
Source/WTF/wtf/text/cf/StringImplCF.cpp

index b88ceaa..7f85696 100644 (file)
 {
     Thread& thread = Thread::current();
     CallbackData *entry = (CallbackData *)thread.m_apiData;
-    if (!entry)
+    // calleeValue may be null if we are initializing a promise.
+    if (!entry || !entry->calleeValue)
         return nil;
     return [JSValue valueWithJSValueRef:entry->calleeValue inContext:[JSContext currentContext]];
 }
index 4c201f8..d8d2df1 100644 (file)
@@ -46,6 +46,9 @@
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
 #include "JSObject.h"
+#include "JSPromise.h"
+#include "JSPromiseDeferred.h"
+#include "JSRetainPtr.h"
 #include "JSString.h"
 #include "JSValueRef.h"
 #include "ObjectConstructor.h"
@@ -274,6 +277,30 @@ JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSV
     return toRef(result);
 }
 
+JSObjectRef JSObjectMakeDeferredPromise(JSContextRef ctx, JSObjectRef* resolve, JSObjectRef* reject, JSValueRef* exception)
+{
+    if (!ctx) {
+        ASSERT_NOT_REACHED();
+        return nullptr;
+    }
+
+    ExecState* exec = toJS(ctx);
+    VM& vm = exec->vm();
+    JSLockHolder locker(exec);
+    auto scope = DECLARE_CATCH_SCOPE(vm);
+
+    auto* globalObject = exec->lexicalGlobalObject();
+    JSPromiseDeferred::DeferredData data = JSPromiseDeferred::createDeferredData(exec, globalObject, globalObject->promiseConstructor());
+    if (handleExceptionIfNeeded(scope, exec, exception) == ExceptionStatus::DidThrow)
+        return nullptr;
+
+    if (resolve)
+        *resolve = toRef(data.resolve);
+    if (reject)
+        *reject = toRef(data.reject);
+    return toRef(data.promise);
+}
+
 JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object)
 {
     if (!ctx) {
index eb68b96..0a95c8f 100644 (file)
@@ -70,6 +70,17 @@ JS_EXPORT bool JSObjectDeletePrivateProperty(JSContextRef ctx, JSObjectRef objec
 JS_EXPORT JSObjectRef JSObjectGetProxyTarget(JSObjectRef);
 
 JS_EXPORT JSGlobalContextRef JSObjectGetGlobalContext(JSObjectRef object);
+
+/*!
+ @function
+ @abstract Creates a JavaScript promise object by invoking the provided executor.
+ @param ctx The execution context to use.
+ @param resolve A pointer to a JSObjectRef in which to store the resolve function for the new promise. Pass NULL if you do not care to store the resolve callback.
+ @param reject A pointer to a JSObjectRef in which to store the reject function for the new promise. Pass NULL if you do not care to store the reject callback.
+ @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception.
+ @result A JSObject that is a promise or NULL if an exception occurred.
+ */
+JS_EXPORT JSObjectRef JSObjectMakeDeferredPromise(JSContextRef ctx, JSObjectRef* resolve, JSObjectRef* reject, JSValueRef* exception) JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
     
 #ifdef __cplusplus
 }
index dc11239..d79742a 100644 (file)
 #import "Exception.h"
 #import "JavaScriptCore.h"
 #import "JSContextInternal.h"
+#import "JSObjectRefPrivate.h"
 #import "JSVirtualMachineInternal.h"
 #import "JSValueInternal.h"
+#import "JSValuePrivate.h"
 #import "JSWrapperMap.h"
 #import "ObjcRuntimeExtras.h"
 #import "JSCInlines.h"
@@ -153,6 +155,48 @@ NSString * const JSPropertyDescriptorSetKey = @"set";
     return [JSValue valueWithJSValueRef:JSValueMakeSymbol([context JSGlobalContextRef], string.get()) inContext:context];
 }
 
++ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *, JSValue *))executor
+{
+    JSObjectRef resolve;
+    JSObjectRef reject;
+    JSValueRef exception = nullptr;
+    JSObjectRef promise = JSObjectMakeDeferredPromise([context JSGlobalContextRef], &resolve, &reject, &exception);
+    if (exception) {
+        [context notifyException:exception];
+        return [JSValue valueWithUndefinedInContext:context];
+    }
+
+    JSValue *result = [JSValue valueWithJSValueRef:promise inContext:context];
+    JSValue *rejection = [JSValue valueWithJSValueRef:reject inContext:context];
+    CallbackData callbackData;
+    const size_t argumentCount = 2;
+    JSValueRef arguments[argumentCount];
+    arguments[0] = resolve;
+    arguments[1] = reject;
+
+    [context beginCallbackWithData:&callbackData calleeValue:nullptr thisValue:promise argumentCount:argumentCount arguments:arguments];
+    executor([JSValue valueWithJSValueRef:resolve inContext:context], rejection);
+    if (context.exception)
+        [rejection callWithArguments:@[context.exception]];
+    [context endCallbackWithData:&callbackData];
+
+    return result;
+}
+
++ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context
+{
+    return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *) {
+        [resolve callWithArguments:@[result]];
+    }];
+}
+
++ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context
+{
+    return [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *reject) {
+        [reject callWithArguments:@[reason]];
+    }];
+}
+
 - (id)toObject
 {
     return valueToObject(_context, m_value);
diff --git a/Source/JavaScriptCore/API/JSValuePrivate.h b/Source/JavaScriptCore/API/JSValuePrivate.h
new file mode 100644 (file)
index 0000000..b26f007
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2018 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if JSC_OBJC_API_ENABLED
+
+#import <JavaScriptCore/JavaScriptCore.h>
+
+@interface JSValue(JSPrivate)
+
+/*!
+ @method
+ @abstract Create a new promise object using the provided executor callback.
+ @param callback A callback block invoked while the promise object is
+ being initialized. The resolve and reject parameters are functions that
+ can be called to notify any pending reactions about the state of the
+ new promise object.
+ @param context The JSContext to which the resulting JSValue belongs.
+ @result The JSValue representing a new promise JavaScript object.
+ @discussion This method is equivalent to calling the Promise constructor in JavaScript.
+ the resolve and reject callbacks each normally take a single value, which they
+ forward to all relevent pending reactions. While inside the executor callback context will act
+ as if it were in any other callback, except calleeFunction will be <code>nil</code>. This also means
+ means the new promise object may be accessed via <code>[context thisValue]</code>.
+ */
++ (JSValue *)valueWithNewPromiseInContext:(JSContext *)context fromExecutor:(void (^)(JSValue *resolve, JSValue *reject))callback JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
+
+/*!
+ @method
+ @abstract Create a new resolved promise object with the provided value.
+ @param result The result value to be passed to any reactions.
+ @param context The JSContext to which the resulting JSValue belongs.
+ @result The JSValue representing a new promise JavaScript object.
+ @discussion This method is equivalent to calling <code>[JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [resolve callWithArguments:@[result]]; } inContext:context]</code>
+ */
++ (JSValue *)valueWithNewPromiseResolvedWithResult:(id)result inContext:(JSContext *)context JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
+
+/*!
+ @method
+ @abstract Create a new rejected promise object with the provided value.
+ @param reason The result value to be passed to any reactions.
+ @param context The JSContext to which the resulting JSValue belongs.
+ @result The JSValue representing a new promise JavaScript object.
+ @discussion This method is equivalent to calling <code>[JSValue valueWithNewPromiseFromExecutor:^(JSValue *resolve, JSValue *reject) { [reject callWithArguments:@[reason]]; } inContext:context]</code>
+ */
++ (JSValue *)valueWithNewPromiseRejectedWithReason:(id)reason inContext:(JSContext *)context JSC_API_AVAILABLE(macosx(JSC_MAC_TBA), ios(JSC_IOS_TBA));
+
+@end
+
+#endif // JSC_OBJC_API_ENABLED
index 672a261..d0c3c60 100644 (file)
 #import "APICast.h"
 #import "DFGWorklist.h"
 #import "JSManagedValueInternal.h"
-#import "JSVirtualMachine.h"
 #import "JSVirtualMachineInternal.h"
+#import "JSVirtualMachinePrivate.h"
 #import "JSWrapperMap.h"
 #import "SigillCrashAnalyzer.h"
 #import "SlotVisitorInlines.h"
 #import <mutex>
+#import <wtf/BlockPtr.h>
 #import <wtf/Lock.h>
 #import <wtf/spi/cocoa/NSMapTableSPI.h>
 
index 6faaf06..8fd2eb7 100644 (file)
@@ -43,7 +43,7 @@
  API may not synchronously free memory.
 */
 
-- (void)shrinkFootprintWhenIdle NS_AVAILABLE(10_14, 12_0);
+- (void)shrinkFootprintWhenIdle JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
 
 #if ENABLE(DFG_JIT)
 
@@ -58,7 +58,7 @@
 @param numberOfThreads The number of threads the DFG compiler should use going forward
 @result The previous number of threads being used by the DFG compiler
 */
-+ (NSUInteger)setNumberOfDFGCompilerThreads:(NSUInteger)numberOfThreads NS_AVAILABLE(10_14, 12_0);
++ (NSUInteger)setNumberOfDFGCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
 
 /*!
 @method
@@ -71,7 +71,7 @@
 @param numberOfThreads The number of threads the FTL compiler should use going forward
 @result The previous number of threads being used by the FTL compiler
 */
-+ (NSUInteger)setNumberOfFTLCompilerThreads:(NSUInteger)numberOfThreads NS_AVAILABLE(10_14, 12_0);
++ (NSUInteger)setNumberOfFTLCompilerThreads:(NSUInteger)numberOfThreads JSC_API_AVAILABLE(macosx(10.14), ios(12.0));
 
 #endif // ENABLE(DFG_JIT)
 
index 8ba0449..6634f6a 100644 (file)
@@ -70,7 +70,7 @@
 void testObjectiveCAPI(void);
 #endif
 
-int testCAPIViaCpp(void);
+int testCAPIViaCpp(const char* filter);
 
 bool assertTrue(bool value, const char* message);
 
@@ -1380,18 +1380,17 @@ int main(int argc, char* argv[])
     SetErrorMode(0);
 #endif
 
-    testCompareAndSwap();
-    startMultithreadedMultiVMExecutionTest();
-
 #if JSC_OBJC_API_ENABLED
     testObjectiveCAPI();
 #endif
-    RELEASE_ASSERT(!testCAPIViaCpp());
 
-    const char *scriptPath = "testapi.js";
-    if (argc > 1) {
-        scriptPath = argv[1];
-    }
+    const char* filter = argc > 1 ? argv[1] : NULL;
+    RELEASE_ASSERT(!testCAPIViaCpp(filter));
+    if (filter)
+        return 0;
+
+    testCompareAndSwap();
+    startMultithreadedMultiVMExecutionTest();
     
     // Test garbage collection with a fresh context
     context = JSGlobalContextCreateInGroup(NULL, NULL);
@@ -1980,6 +1979,7 @@ int main(int argc, char* argv[])
     JSObjectMakeConstructor(context, nullClass, 0);
     JSClassRelease(nullClass);
 
+    const char* scriptPath = "testapi.js";
     char* scriptUTF8 = createStringWithContentsOfFile(scriptPath);
     if (!scriptUTF8) {
         printf("FAIL: Test script could not be loaded.\n");
index 8a5777c..76122cd 100644 (file)
 #include "JSCJSValueInlines.h"
 #include "JSObject.h"
 
+#include <JavaScriptCore/JSObjectRefPrivate.h>
 #include <JavaScriptCore/JavaScript.h>
 #include <wtf/DataLog.h>
 #include <wtf/Expected.h>
 #include <wtf/Noncopyable.h>
+#include <wtf/NumberOfCores.h>
 #include <wtf/Vector.h>
 
-extern "C" int testCAPIViaCpp();
+extern "C" int testCAPIViaCpp(const char* filter);
 
 class APIString {
     WTF_MAKE_NONCOPYABLE(APIString);
@@ -83,6 +85,7 @@ public:
     }
 
     operator JSGlobalContextRef() { return m_context; }
+    operator JSC::ExecState*() { return toJS(m_context); }
 
 private:
     JSGlobalContextRef m_context;
@@ -122,7 +125,19 @@ private:
 
 class TestAPI {
 public:
-    int run();
+    int run(const char* filter);
+
+    void basicSymbol();
+    void symbolsTypeof();
+    void symbolsGetPropertyForKey();
+    void symbolsSetPropertyForKey();
+    void symbolsHasPropertyForKey();
+    void symbolsDeletePropertyForKey();
+    void promiseResolveTrue();
+    void promiseRejectTrue();
+
+    int failed() const { return m_failed; }
+
 private:
 
     template<typename... Strings>
@@ -143,16 +158,10 @@ private:
     APIVector<JSObjectRef> interestingObjects();
     APIVector<JSValueRef> interestingKeys();
 
-    int failed { 0 };
+    int m_failed { 0 };
     APIContext context;
 };
 
-int testCAPIViaCpp()
-{
-    TestAPI tester;
-    return tester.run();
-}
-
 TestAPI::ScriptResult TestAPI::evaluateScript(const char* script, JSObjectRef thisObject)
 {
     APIString scriptAPIString(script);
@@ -202,7 +211,7 @@ bool TestAPI::check(bool condition, Strings... messages)
 {
     if (!condition) {
         dataLogLn(messages..., ": FAILED");
-        failed++;
+        m_failed++;
     } else
         dataLogLn(messages..., ": PASSED");
 
@@ -257,139 +266,269 @@ APIVector<JSValueRef> TestAPI::interestingKeys()
     return result;
 }
 
-int TestAPI::run()
+static const char* isSymbolFunction = "(function isSymbol(symbol) { return typeof(symbol) === 'symbol'; })";
+static const char* getFunction = "(function get(object, key) { return object[key]; })";
+static const char* setFunction = "(function set(object, key, value) { object[key] = value; })";
+
+void TestAPI::basicSymbol()
 {
-    dataLogLn("Starting C-API tests in C++");
-    const char* isSymbolFunction = "(function isSymbol(symbol) { return typeof(symbol) === 'symbol'; })";
-    const char* getFunction = "(function get(object, key) { return object[key]; })";
-    const char* setFunction = "(function set(object, key, value) { object[key] = value; })";
-    const char* hasFunction = "(function has(object, key) { return key in object; })";
-    const char* deleteFunction = "(function del(object, key) { return delete object[key]; })";
+    // Can't call Symbol as a constructor since it's not subclassable.
+    auto result = evaluateScript("Symbol('dope');");
+    check(JSValueGetType(context, result.value()) == kJSTypeSymbol, "dope get type is a symbol");
+    check(JSValueIsSymbol(context, result.value()), "dope is a symbol");
+}
 
-    JSC::ExecState* exec = toJS(context);
+void TestAPI::symbolsTypeof()
+{
+    APIString description("dope");
+    JSValueRef symbol = JSValueMakeSymbol(context, description);
+    check(functionReturnsTrue(isSymbolFunction, symbol), "JSValueMakeSymbol makes a symbol value");
+}
 
-    {
-        // Can't call Symbol as a constructor since it's not subclassable.
-        auto result = evaluateScript("Symbol('dope');");
-        check(JSValueGetType(context, result.value()) == kJSTypeSymbol, "dope get type is a symbol");
-        check(JSValueIsSymbol(context, result.value()), "dope is a symbol");
+void TestAPI::symbolsGetPropertyForKey()
+{
+    auto objects = interestingObjects();
+    auto keys = interestingKeys();
+
+    for (auto& object : objects) {
+        dataLogLn("\nnext object: ", toJS(context, object));
+        for (auto& key : keys) {
+            dataLogLn("Using key: ", toJS(context, key));
+            checkJSAndAPIMatch(
+            [&] {
+                return callFunction(getFunction, object, key);
+            }, [&] (JSValueRef* exception) {
+                return JSObjectGetPropertyForKey(context, object, key, exception);
+            }, "checking get property keys");
+        }
     }
+}
 
-    {
-        APIString description("dope");
-        JSValueRef symbol = JSValueMakeSymbol(context, description);
-        check(functionReturnsTrue(isSymbolFunction, symbol), "JSValueMakeSymbol makes a symbol value");
+void TestAPI::symbolsSetPropertyForKey()
+{
+    auto jsObjects = interestingObjects();
+    auto apiObjects = interestingObjects();
+    auto keys = interestingKeys();
+
+    JSValueRef theAnswer = JSValueMakeNumber(context, 42);
+    for (size_t i = 0; i < jsObjects.size(); i++) {
+        for (auto& key : keys) {
+            JSObjectRef jsObject = jsObjects[i];
+            JSObjectRef apiObject = apiObjects[i];
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(setFunction, jsObject, key, theAnswer);
+                } , [&] (JSValueRef* exception) {
+                    JSObjectSetPropertyForKey(context, apiObject, key, theAnswer, kJSPropertyAttributeNone, exception);
+                    return JSValueMakeUndefined(context);
+                }, "setting property keys to the answer");
+            // Check get is the same on API object.
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(getFunction, apiObject, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSObjectGetPropertyForKey(context, apiObject, key, exception);
+                }, "getting property keys from API objects");
+            // Check get is the same on respective objects.
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(getFunction, jsObject, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSObjectGetPropertyForKey(context, apiObject, key, exception);
+                }, "getting property keys from respective objects");
+        }
     }
+}
 
-    {
-        auto objects = interestingObjects();
-        auto keys = interestingKeys();
-
-        for (auto& object : objects) {
-            dataLogLn("\nnext object: ", toJS(exec, object));
-            for (auto& key : keys) {
-                dataLogLn("Using key: ", toJS(exec, key));
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(getFunction, object, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSObjectGetPropertyForKey(context, object, key, exception);
-                    }, "checking get property keys");
-            }
+void TestAPI::symbolsHasPropertyForKey()
+{
+    const char* hasFunction = "(function has(object, key) { return key in object; })";
+    auto objects = interestingObjects();
+    auto keys = interestingKeys();
+
+    JSValueRef theAnswer = JSValueMakeNumber(context, 42);
+    for (auto& object : objects) {
+        dataLogLn("\nNext object: ", toJS(context, object));
+        for (auto& key : keys) {
+            dataLogLn("Using key: ", toJS(context, key));
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(hasFunction, object, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
+                }, "checking has property keys unset");
+
+            check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
+
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(hasFunction, object, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
+                }, "checking has property keys set");
         }
     }
+}
 
-    {
-        auto jsObjects = interestingObjects();
-        auto apiObjects = interestingObjects();
-        auto keys = interestingKeys();
-
-        JSValueRef theAnswer = JSValueMakeNumber(context, 42);
-        for (size_t i = 0; i < jsObjects.size(); i++) {
-            for (auto& key : keys) {
-                JSObjectRef jsObject = jsObjects[i];
-                JSObjectRef apiObject = apiObjects[i];
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(setFunction, jsObject, key, theAnswer);
-                    } , [&] (JSValueRef* exception) {
-                        JSObjectSetPropertyForKey(context, apiObject, key, theAnswer, kJSPropertyAttributeNone, exception);
-                        return JSValueMakeUndefined(context);
-                    }, "setting property keys to the answer");
-                // Check get is the same on API object.
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(getFunction, apiObject, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSObjectGetPropertyForKey(context, apiObject, key, exception);
-                    }, "getting property keys from API objects");
-                // Check get is the same on respective objects.
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(getFunction, jsObject, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSObjectGetPropertyForKey(context, apiObject, key, exception);
-                    }, "getting property keys from respective objects");
-            }
+
+void TestAPI::symbolsDeletePropertyForKey()
+{
+    const char* deleteFunction = "(function del(object, key) { return delete object[key]; })";
+    auto objects = interestingObjects();
+    auto keys = interestingKeys();
+
+    JSValueRef theAnswer = JSValueMakeNumber(context, 42);
+    for (auto& object : objects) {
+        dataLogLn("\nNext object: ", toJS(context, object));
+        for (auto& key : keys) {
+            dataLogLn("Using key: ", toJS(context, key));
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(deleteFunction, object, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
+                }, "checking has property keys unset");
+
+            check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
+
+            checkJSAndAPIMatch(
+                [&] {
+                    return callFunction(deleteFunction, object, key);
+                }, [&] (JSValueRef* exception) {
+                    return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
+                }, "checking has property keys set");
         }
     }
+}
 
-    {
-        auto objects = interestingObjects();
-        auto keys = interestingKeys();
-
-        JSValueRef theAnswer = JSValueMakeNumber(context, 42);
-        for (auto& object : objects) {
-            dataLogLn("\nNext object: ", toJS(exec, object));
-            for (auto& key : keys) {
-                dataLogLn("Using key: ", toJS(exec, key));
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(hasFunction, object, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
-                    }, "checking has property keys unset");
-
-                check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
-
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(hasFunction, object, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSValueMakeBoolean(context, JSObjectHasPropertyForKey(context, object, key, exception));
-                    }, "checking has property keys set");
-            }
-        }
+void TestAPI::promiseResolveTrue()
+{
+    JSObjectRef resolve;
+    JSObjectRef reject;
+    JSValueRef exception = nullptr;
+    JSObjectRef promise = JSObjectMakeDeferredPromise(context, &resolve, &reject, &exception);
+    check(!exception, "No exception should be thrown creating a deferred promise");
+
+    // Ugh, we don't have any C API that takes blocks... so we do this hack to capture the runner.
+    static TestAPI* tester = this;
+    static bool passedTrueCalled = false;
+
+    APIString trueString("passedTrue");
+    auto passedTrue = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) -> JSValueRef {
+        tester->check(argumentCount && JSValueIsStrictEqual(ctx, arguments[0], JSValueMakeBoolean(ctx, true)), "function should have been called back with true");
+        passedTrueCalled = true;
+        return JSValueMakeUndefined(ctx);
+    };
+
+    APIString thenString("then");
+    JSValueRef thenFunction = JSObjectGetProperty(context, promise, thenString, &exception);
+    check(!exception && thenFunction && JSValueIsObject(context, thenFunction), "Promise should have a then object property");
+
+    JSValueRef passedTrueFunction = JSObjectMakeFunctionWithCallback(context, trueString, passedTrue);
+    JSObjectCallAsFunction(context, const_cast<JSObjectRef>(thenFunction), promise, 1, &passedTrueFunction, &exception);
+    check(!exception, "No exception should be thrown setting up callback");
+
+    auto trueValue = JSValueMakeBoolean(context, true);
+    JSObjectCallAsFunction(context, resolve, resolve, 1, &trueValue, &exception);
+    check(!exception, "No exception should be thrown resolve promise");
+    check(passedTrueCalled, "then response function should have been called.");
+}
+
+void TestAPI::promiseRejectTrue()
+{
+    JSObjectRef resolve;
+    JSObjectRef reject;
+    JSValueRef exception = nullptr;
+    JSObjectRef promise = JSObjectMakeDeferredPromise(context, &resolve, &reject, &exception);
+    check(!exception, "No exception should be thrown creating a deferred promise");
+
+    // Ugh, we don't have any C API that takes blocks... so we do this hack to capture the runner.
+    static TestAPI* tester = this;
+    static bool passedTrueCalled = false;
+
+    APIString trueString("passedTrue");
+    auto passedTrue = [](JSContextRef ctx, JSObjectRef, JSObjectRef, size_t argumentCount, const JSValueRef arguments[], JSValueRef*) -> JSValueRef {
+        tester->check(argumentCount && JSValueIsStrictEqual(ctx, arguments[0], JSValueMakeBoolean(ctx, true)), "function should have been called back with true");
+        passedTrueCalled = true;
+        return JSValueMakeUndefined(ctx);
+    };
+
+    APIString catchString("catch");
+    JSValueRef catchFunction = JSObjectGetProperty(context, promise, catchString, &exception);
+    check(!exception && catchFunction && JSValueIsObject(context, catchFunction), "Promise should have a then object property");
+
+    JSValueRef passedTrueFunction = JSObjectMakeFunctionWithCallback(context, trueString, passedTrue);
+    JSObjectCallAsFunction(context, const_cast<JSObjectRef>(catchFunction), promise, 1, &passedTrueFunction, &exception);
+    check(!exception, "No exception should be thrown setting up callback");
+
+    auto trueValue = JSValueMakeBoolean(context, true);
+    JSObjectCallAsFunction(context, reject, reject, 1, &trueValue, &exception);
+    check(!exception, "No exception should be thrown resolve promise");
+    check(passedTrueCalled, "then response function should have been called.");
+}
+
+#define RUN(test) do {                                 \
+        if (!shouldRun(#test))                         \
+            break;                                     \
+        tasks.append(                                  \
+            createSharedTask<void(TestAPI&)>(          \
+                [&] (TestAPI& tester) {                \
+                    tester.test;                       \
+                    dataLog(#test ": OK!\n");          \
+                }));                                   \
+    } while (false)
+
+int testCAPIViaCpp(const char* filter)
+{
+    dataLogLn("Starting C-API tests in C++");
+
+    Deque<RefPtr<SharedTask<void(TestAPI&)>>> tasks;
+
+    auto shouldRun = [&] (const char* testName) -> bool {
+        return !filter || !!strcasestr(testName, filter);
+    };
+
+    RUN(basicSymbol());
+    RUN(symbolsTypeof());
+    RUN(symbolsGetPropertyForKey());
+    RUN(symbolsSetPropertyForKey());
+    RUN(symbolsHasPropertyForKey());
+    RUN(symbolsDeletePropertyForKey());
+    RUN(promiseResolveTrue());
+    RUN(promiseRejectTrue());
+
+    if (tasks.isEmpty()) {
+        dataLogLn("Filtered all tests: ERROR");
+        return 1;
     }
 
-    {
-        auto objects = interestingObjects();
-        auto keys = interestingKeys();
-
-        JSValueRef theAnswer = JSValueMakeNumber(context, 42);
-        for (auto& object : objects) {
-            dataLogLn("\nNext object: ", toJS(exec, object));
-            for (auto& key : keys) {
-                dataLogLn("Using key: ", toJS(exec, key));
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(deleteFunction, object, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
-                    }, "checking has property keys unset");
-
-                check(!!callFunction(setFunction, object, key, theAnswer), "set property to the answer");
-
-                checkJSAndAPIMatch(
-                    [&] {
-                        return callFunction(deleteFunction, object, key);
-                    }, [&] (JSValueRef* exception) {
-                        return JSValueMakeBoolean(context, JSObjectDeletePropertyForKey(context, object, key, exception));
-                    }, "checking has property keys set");
-            }
-        }
+    Lock lock;
+
+    static Atomic<int> failed { 0 };
+    Vector<Ref<Thread>> threads;
+    for (unsigned i = filter ? 1 : WTF::numberOfProcessorCores(); i--;) {
+        threads.append(Thread::create(
+            "Testapi via C++ thread",
+            [&] () {
+                TestAPI tester;
+                for (;;) {
+                    RefPtr<SharedTask<void(TestAPI&)>> task;
+                    {
+                        LockHolder locker(lock);
+                        if (tasks.isEmpty())
+                            return;
+                        task = tasks.takeFirst();
+                    }
+
+                    task->run(tester);
+                }
+                failed.exchangeAdd(tester.failed());
+            }));
     }
 
-    dataLogLn("C-API tests in C++ had ", failed, " failures");
-    return failed;
+    for (auto& thread : threads)
+        thread->waitForCompletion();
+
+    dataLogLn("C-API tests in C++ had ", failed.load(), " failures");
+    return failed.load();
 }
index 3828054..37e1385 100644 (file)
 #import "JSExportMacros.h"
 #import <JavaScriptCore/JavaScriptCore.h>
 
-#undef NS_AVAILABLE
-#define NS_AVAILABLE(_mac, _ios)
-
 #import "CurrentThisInsideBlockGetterTest.h"
 #import "DFGWorklist.h"
 #import "DateTests.h"
+#import "JSCast.h"
 #import "JSExportTests.h"
+#import "JSValuePrivate.h"
+#import "JSVirtualMachineInternal.h"
 #import "JSVirtualMachinePrivate.h"
 #import "JSWrapperMapTests.h"
 #import "Regress141275.h"
@@ -556,8 +556,6 @@ static void runJITThreadLimitTests()
 
 static void testObjectiveCAPIMain()
 {
-    runJITThreadLimitTests();
-
     @autoreleasepool {
         JSVirtualMachine* vm = [[JSVirtualMachine alloc] init];
         JSContext* context = [[JSContext alloc] initWithVirtualMachine:vm];
@@ -1653,11 +1651,182 @@ static void checkNegativeNSIntegers()
     checkResult(@"Negative number maintained its original value", [[result toString] isEqualToString:@"-1"]);
 }
 
+enum class Resolution {
+    ResolveEager,
+    RejectEager,
+    ResolveLate,
+    RejectLate,
+};
+
+static void promiseWithExecutor(Resolution resolution)
+{
+    @autoreleasepool {
+        JSContext *context = [[JSContext alloc] init];
+
+        __block JSValue *resolveCallback;
+        __block JSValue *rejectCallback;
+        JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *reject) {
+            if (resolution == Resolution::ResolveEager)
+                [resolve callWithArguments:@[@YES]];
+            if (resolution == Resolution::RejectEager)
+                [reject callWithArguments:@[@YES]];
+            resolveCallback = resolve;
+            rejectCallback = reject;
+        }];
+
+        __block bool valueWasResolvedTrue = false;
+        __block bool valueWasRejectedTrue = false;
+        [promise invokeMethod:@"then" withArguments:@[
+            ^(JSValue *value) { valueWasResolvedTrue = [value isBoolean] && [value toBool]; },
+            ^(JSValue *value) { valueWasRejectedTrue = [value isBoolean] && [value toBool]; },
+        ]];
+
+        switch (resolution) {
+        case Resolution::ResolveEager:
+            checkResult(@"ResolveEager should have set resolve early.", valueWasResolvedTrue && !valueWasRejectedTrue);
+            break;
+        case Resolution::RejectEager:
+            checkResult(@"RejectEager should have set reject early.", !valueWasResolvedTrue && valueWasRejectedTrue);
+            break;
+        default:
+            checkResult(@"Resolve/RejectLate should have not have set anything early.", !valueWasResolvedTrue && !valueWasRejectedTrue);
+            break;
+        }
+
+        valueWasResolvedTrue = false;
+        valueWasRejectedTrue = false;
+
+        // Run script to make sure reactions don't happen again
+        [context evaluateScript:@"{ };"];
+
+        if (resolution == Resolution::ResolveLate)
+            [resolveCallback callWithArguments:@[@YES]];
+        if (resolution == Resolution::RejectLate)
+            [rejectCallback callWithArguments:@[@YES]];
+
+        switch (resolution) {
+        case Resolution::ResolveLate:
+            checkResult(@"ResolveLate should have set resolve late.", valueWasResolvedTrue && !valueWasRejectedTrue);
+            break;
+        case Resolution::RejectLate:
+            checkResult(@"RejectLate should have set reject late.", !valueWasResolvedTrue && valueWasRejectedTrue);
+            break;
+        default:
+            checkResult(@"Resolve/RejectEarly should have not have set anything late.", !valueWasResolvedTrue && !valueWasRejectedTrue);
+            break;
+        }
+    }
+}
+
+static void promiseRejectOnJSException()
+{
+    @autoreleasepool {
+        JSContext *context = [[JSContext alloc] init];
+
+        JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *) {
+            context.exception = [JSValue valueWithNewErrorFromMessage:@"dope" inContext:context];
+        }];
+        checkResult(@"Exception set in callback should not propagate", !context.exception);
+
+        __block bool reasonWasObject = false;
+        [promise invokeMethod:@"catch" withArguments:@[^(JSValue *reason) { reasonWasObject = [reason isObject]; }]];
+
+        checkResult(@"Setting an exception in executor causes the promise to be rejected", reasonWasObject);
+
+        promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *, JSValue *) {
+            [context evaluateScript:@"throw new Error('dope');"];
+        }];
+        checkResult(@"Exception thrown in callback should not propagate", !context.exception);
+
+        reasonWasObject = false;
+        [promise invokeMethod:@"catch" withArguments:@[^(JSValue *reason) { reasonWasObject = [reason isObject]; }]];
+
+        checkResult(@"Running code that throws an exception in the executor causes the promise to be rejected", reasonWasObject);
+    }
+}
+
+static void promiseCreateResolved()
+{
+    @autoreleasepool {
+        JSContext *context = [[JSContext alloc] init];
+
+        JSValue *promise = [JSValue valueWithNewPromiseResolvedWithResult:[NSNull null] inContext:context];
+        __block bool calledWithNull = false;
+        [promise invokeMethod:@"then" withArguments:@[
+            ^(JSValue *result) { calledWithNull = [result isNull]; }
+        ]];
+
+        checkResult(@"ResolvedPromise should actually resolve the promise", calledWithNull);
+    }
+}
+
+static void promiseCreateRejected()
+{
+    @autoreleasepool {
+        JSContext *context = [[JSContext alloc] init];
+
+        JSValue *promise = [JSValue valueWithNewPromiseRejectedWithReason:[NSNull null] inContext:context];
+        __block bool calledWithNull = false;
+        [promise invokeMethod:@"then" withArguments:@[
+            [NSNull null],
+            ^(JSValue *result) { calledWithNull = [result isNull]; }
+        ]];
+
+        checkResult(@"RejectedPromise should actually reject the promise", calledWithNull);
+    }
+}
+
+static void parallelPromiseResolveTest()
+{
+    @autoreleasepool {
+        JSContext *context = [[JSContext alloc] init];
+
+        __block RefPtr<Thread> thread;
+
+        Atomic<bool> shouldResolveSoon { false };
+        Atomic<bool> startedThread { false };
+        auto* shouldResolveSoonPtr = &shouldResolveSoon;
+        auto* startedThreadPtr = &startedThread;
+
+        JSValue *promise = [JSValue valueWithNewPromiseInContext:context fromExecutor:^(JSValue *resolve, JSValue *) {
+            thread = Thread::create("async thread", ^() {
+                startedThreadPtr->store(true);
+                while (!shouldResolveSoonPtr->load()) { }
+                [resolve callWithArguments:@[[NSNull null]]];
+            });
+
+        }];
+
+        shouldResolveSoon.store(true);
+        while (!startedThread.load())
+            [context evaluateScript:@"for (let i = 0; i < 10000; i++) { }"];
+
+        thread->waitForCompletion();
+
+        __block bool calledWithNull = false;
+        [promise invokeMethod:@"then" withArguments:@[
+            ^(JSValue *result) { calledWithNull = [result isNull]; }
+        ]];
+
+        checkResult(@"Promise should be resolved", calledWithNull);
+    }
+}
 
 void testObjectiveCAPI()
 {
     NSLog(@"Testing Objective-C API");
     checkNegativeNSIntegers();
+    runJITThreadLimitTests();
+
+    promiseWithExecutor(Resolution::ResolveEager);
+    promiseWithExecutor(Resolution::RejectEager);
+    promiseWithExecutor(Resolution::ResolveLate);
+    promiseWithExecutor(Resolution::RejectLate);
+    promiseRejectOnJSException();
+    promiseCreateResolved();
+    promiseCreateRejected();
+    parallelPromiseResolveTest();
+
     testObjectiveCAPIMain();
 }
 
index 553cd58..81f9cad 100644 (file)
@@ -1,3 +1,90 @@
+2018-09-21  Keith Miller  <keith_miller@apple.com>
+
+        Add Promise SPI
+        https://bugs.webkit.org/show_bug.cgi?id=189809
+
+        Reviewed by Saam Barati.
+
+        The Patch adds new SPI to create promises. It's mostly SPI because
+        I want to see how internal users react to it before we make it
+        public.
+
+        This patch adds a couple of new Obj-C SPI methods. The first
+        creates a new promise using the same API that JS does where the
+        user provides an executor callback. If an exception is raised
+        in/to that callback the promise is automagically rejected. The
+        other methods create a pre-resolved or rejected promise as this
+        appears to be a common way to initialize a promise.
+
+        I was also considering adding a second version of executor API
+        where it would catch specific Obj-C exceptions. This would work by
+        taking a Class paramter and checking isKindOfClass: on the
+        exception. I decided against this as nothing else in our API
+        handles Obj-C exceptions. I'm pretty sure the VM will end up in a
+        corrupt state if an Obj-C exception unwinds through JS frames.
+
+        This patch adds a new C function that will create a "deferred"
+        promise. A deferred promise is a style of creating promise/futures
+        where the resolve and reject functions are passed as outputs of a
+        function. I went with this style for the C SPI because we don't have
+        any concept of forwarding exceptions in the C API.
+
+        In order to make the C API work I refactored a bit of the promise code
+        so that we can call a static method on JSDeferredPromise and just get
+        the components without allocating an extra cell wrapper.
+
+        * API/JSContext.mm:
+        (+[JSContext currentCallee]):
+        * API/JSObjectRef.cpp:
+        (JSObjectMakeDeferredPromise):
+        * API/JSObjectRefPrivate.h:
+        * API/JSValue.mm:
+        (+[JSValue valueWithNewPromiseInContext:fromExecutor:]):
+        (+[JSValue valueWithNewPromiseResolvedWithResult:inContext:]):
+        (+[JSValue valueWithNewPromiseRejectedWithReason:inContext:]):
+        * API/JSValuePrivate.h: Added.
+        * API/JSVirtualMachine.mm:
+        * API/JSVirtualMachinePrivate.h:
+        * API/tests/testapi.c:
+        (main):
+        * API/tests/testapi.cpp:
+        (APIContext::operator JSC::ExecState*):
+        (TestAPI::failed const):
+        (TestAPI::check):
+        (TestAPI::basicSymbol):
+        (TestAPI::symbolsTypeof):
+        (TestAPI::symbolsGetPropertyForKey):
+        (TestAPI::symbolsSetPropertyForKey):
+        (TestAPI::symbolsHasPropertyForKey):
+        (TestAPI::symbolsDeletePropertyForKey):
+        (TestAPI::promiseResolveTrue):
+        (TestAPI::promiseRejectTrue):
+        (testCAPIViaCpp):
+        (TestAPI::run): Deleted.
+        * API/tests/testapi.mm:
+        (testObjectiveCAPIMain):
+        (promiseWithExecutor):
+        (promiseRejectOnJSException):
+        (promiseCreateResolved):
+        (promiseCreateRejected):
+        (parallelPromiseResolveTest):
+        (testObjectiveCAPI):
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * runtime/JSInternalPromiseDeferred.cpp:
+        (JSC::JSInternalPromiseDeferred::create):
+        * runtime/JSPromise.h:
+        * runtime/JSPromiseConstructor.cpp:
+        (JSC::constructPromise):
+        * runtime/JSPromiseDeferred.cpp:
+        (JSC::JSPromiseDeferred::createDeferredData):
+        (JSC::JSPromiseDeferred::create):
+        (JSC::JSPromiseDeferred::finishCreation):
+        (JSC::newPromiseCapability): Deleted.
+        * runtime/JSPromiseDeferred.h:
+        (JSC::JSPromiseDeferred::promise const):
+        (JSC::JSPromiseDeferred::resolve const):
+        (JSC::JSPromiseDeferred::reject const):
+
 2018-09-21  Truitt Savell  <tsavell@apple.com>
 
         Rebaseline tests after changes in https://trac.webkit.org/changeset/236321/webkit
index 9c5c50c..37787f2 100644 (file)
                53C6FEEF1E8ADFA900B18425 /* WasmOpcodeOrigin.h in Headers */ = {isa = PBXBuildFile; fileRef = 53C6FEEE1E8ADFA900B18425 /* WasmOpcodeOrigin.h */; };
                53CA730A1EA533D80076049D /* WasmBBQPlan.h in Headers */ = {isa = PBXBuildFile; fileRef = 53CA73081EA533D80076049D /* WasmBBQPlan.h */; };
                53D444DC1DAF08AB00B92784 /* B3WasmAddressValue.h in Headers */ = {isa = PBXBuildFile; fileRef = 53D444DB1DAF08AB00B92784 /* B3WasmAddressValue.h */; };
+               53E1F8F82154715A0001DDBC /* JSValuePrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E1F8F7215471490001DDBC /* JSValuePrivate.h */; settings = {ATTRIBUTES = (Private, ); }; };
                53E777E41E92E265007CBEC4 /* WasmModuleInformation.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E777E21E92E265007CBEC4 /* WasmModuleInformation.h */; };
                53E9E0AC1EAE83DF00FEE251 /* WasmMachineThreads.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E9E0AA1EAE83DE00FEE251 /* WasmMachineThreads.h */; };
                53E9E0AF1EAEC45700FEE251 /* WasmTierUpCount.h in Headers */ = {isa = PBXBuildFile; fileRef = 53E9E0AE1EAEC45700FEE251 /* WasmTierUpCount.h */; settings = {ATTRIBUTES = (Private, ); }; };
                53CA73081EA533D80076049D /* WasmBBQPlan.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmBBQPlan.h; sourceTree = "<group>"; };
                53D444DB1DAF08AB00B92784 /* B3WasmAddressValue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = B3WasmAddressValue.h; path = b3/B3WasmAddressValue.h; sourceTree = "<group>"; };
                53D444DD1DAF09A000B92784 /* B3WasmAddressValue.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = B3WasmAddressValue.cpp; path = b3/B3WasmAddressValue.cpp; sourceTree = "<group>"; };
+               53E1F8F7215471490001DDBC /* JSValuePrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSValuePrivate.h; sourceTree = "<group>"; };
                53E777E11E92E265007CBEC4 /* WasmModuleInformation.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmModuleInformation.cpp; sourceTree = "<group>"; };
                53E777E21E92E265007CBEC4 /* WasmModuleInformation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmModuleInformation.h; sourceTree = "<group>"; };
                53E9E0A91EAE83DE00FEE251 /* WasmMachineThreads.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmMachineThreads.cpp; sourceTree = "<group>"; };
                                86E3C606167BAB87006D760A /* JSValue.h */,
                                86E3C60D167BAB87006D760A /* JSValue.mm */,
                                86E3C60E167BAB87006D760A /* JSValueInternal.h */,
+                               53E1F8F7215471490001DDBC /* JSValuePrivate.h */,
                                14BD5A2B0A3E91F600BAF59C /* JSValueRef.cpp */,
                                1482B6EA0A4300B300517CFC /* JSValueRef.h */,
                                86E3C60F167BAB87006D760A /* JSVirtualMachine.h */,
                        isa = PBXGroup;
                        children = (
                                99DA00991BD5992700F4575C /* __init__.py */,
-                               99DA009D1BD5992700F4575C /* wkbuiltins.py */,
                                99DA009E1BD5992700F4575C /* builtins_generate_combined_header.py */,
                                99DA009F1BD5992700F4575C /* builtins_generate_combined_implementation.py */,
                                412952731D2CF6AC00E78B89 /* builtins_generate_internals_wrapper_header.py */,
                                99DA009A1BD5992700F4575C /* builtins_generator.py */,
                                99DA009B1BD5992700F4575C /* builtins_model.py */,
                                99DA009C1BD5992700F4575C /* builtins_templates.py */,
+                               99DA009D1BD5992700F4575C /* wkbuiltins.py */,
                        );
                        path = wkbuiltins;
                        sourceTree = "<group>";
                                DE26E9031CB5DD0500D2BE82 /* BuiltinExecutableCreator.h in Headers */,
                                A7D801A51880D66E0026C39B /* BuiltinExecutables.h in Headers */,
                                A75EE9B218AAB7E200AAD043 /* BuiltinNames.h in Headers */,
-                               99DA00A61BD5993100F4575C /* wkbuiltins.py in Headers */,
                                99DA00A71BD5993100F4575C /* builtins_generate_combined_header.py in Headers */,
                                99DA00A81BD5993100F4575C /* builtins_generate_combined_implementation.py in Headers */,
                                412952771D2CF6BC00E78B89 /* builtins_generate_internals_wrapper_header.py in Headers */,
                                0F2B670117B6B5AB00A7AE3F /* JSUint8ClampedArray.h in Headers */,
                                86E3C612167BABD7006D760A /* JSValue.h in Headers */,
                                86E3C61B167BABEE006D760A /* JSValueInternal.h in Headers */,
+                               53E1F8F82154715A0001DDBC /* JSValuePrivate.h in Headers */,
                                BC18C42C0E16F5CD00B34460 /* JSValueRef.h in Headers */,
                                86E3C615167BABD7006D760A /* JSVirtualMachine.h in Headers */,
                                86E3C61D167BABEE006D760A /* JSVirtualMachineInternal.h in Headers */,
                                ADBC54D51DF8EA2B005BF738 /* WebAssemblyToJSCallee.h in Headers */,
                                52F6C35E1E71EB080081F4CC /* WebAssemblyWrapperFunction.h in Headers */,
                                BC18C47A0E16F5CD00B34460 /* WebKitAvailability.h in Headers */,
+                               99DA00A61BD5993100F4575C /* wkbuiltins.py in Headers */,
                                A7DCB97312E5193F00911940 /* WriteBarrier.h in Headers */,
                                C2B6D75318A33793004A9301 /* WriteBarrierInlines.h in Headers */,
                                0FC8150A14043BF500CFA603 /* WriteBarrierSupport.h in Headers */,
index 449cc9e..92c64ef 100644 (file)
@@ -42,19 +42,11 @@ JSInternalPromiseDeferred* JSInternalPromiseDeferred::create(ExecState* exec, JS
     VM& vm = exec->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    JSValue deferred = newPromiseCapability(exec, globalObject, globalObject->internalPromiseConstructor());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-
-    JSValue promise = deferred.get(exec, vm.propertyNames->builtinNames().promisePrivateName());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-    ASSERT(promise.inherits<JSInternalPromise>(vm));
-    JSValue resolve = deferred.get(exec, vm.propertyNames->builtinNames().resolvePrivateName());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-    JSValue reject = deferred.get(exec, vm.propertyNames->builtinNames().rejectPrivateName());
+    DeferredData data = createDeferredData(exec, globalObject, globalObject->internalPromiseConstructor());
     RETURN_IF_EXCEPTION(scope, nullptr);
 
     JSInternalPromiseDeferred* result = new (NotNull, allocateCell<JSInternalPromiseDeferred>(vm.heap)) JSInternalPromiseDeferred(vm);
-    result->finishCreation(vm, jsCast<JSObject*>(promise), resolve, reject);
+    result->finishCreation(vm, data.promise, data.resolve, data.reject);
     return result;
 }
 
index 7056eb9..9fc2e4c 100644 (file)
@@ -34,6 +34,13 @@ public:
     typedef JSNonFinalObject Base;
 
     static JSPromise* create(VM&, Structure*);
+    struct JSPromiseAndCallbacks {
+        JSPromise* promise;
+        JSFunction* resolve;
+        JSFunction* reject;
+    };
+    static JSPromiseAndCallbacks createWithCallbacks(VM&, Structure*);
+
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
 
     DECLARE_EXPORT_INFO;
index 250fc68..89b7de1 100644 (file)
@@ -114,7 +114,7 @@ static EncodedJSValue JSC_HOST_CALL constructPromise(ExecState* exec)
     Structure* promiseStructure = InternalFunction::createSubclassStructure(exec, exec->newTarget(), globalObject->promiseStructure());
     RETURN_IF_EXCEPTION(scope, encodedJSValue());
     JSPromise* promise = JSPromise::create(vm, promiseStructure);
-    promise->initialize(exec, globalObject, exec->argument(0));
+    promise->initialize(exec, globalObject,  exec->argument(0));
     RETURN_IF_EXCEPTION(scope, encodedJSValue());
 
     return JSValue::encode(promise);
index 147a949..3455eaa 100644 (file)
@@ -39,8 +39,11 @@ namespace JSC {
 
 const ClassInfo JSPromiseDeferred::s_info = { "JSPromiseDeferred", nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(JSPromiseDeferred) };
 
-JSValue newPromiseCapability(ExecState* exec, JSGlobalObject* globalObject, JSPromiseConstructor* promiseConstructor)
+JSPromiseDeferred::DeferredData JSPromiseDeferred::createDeferredData(ExecState* exec, JSGlobalObject* globalObject, JSPromiseConstructor* promiseConstructor)
 {
+    VM& vm = exec->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
     JSFunction* newPromiseCapabilityFunction = globalObject->newPromiseCapabilityFunction();
     CallData callData;
     CallType callType = JSC::getCallData(exec->vm(), newPromiseCapabilityFunction, callData);
@@ -49,30 +52,31 @@ JSValue newPromiseCapability(ExecState* exec, JSGlobalObject* globalObject, JSPr
     MarkedArgumentBuffer arguments;
     arguments.append(promiseConstructor);
     ASSERT(!arguments.hasOverflowed());
-    return call(exec, newPromiseCapabilityFunction, callType, callData, jsUndefined(), arguments);
+    JSValue deferred = call(exec, newPromiseCapabilityFunction, callType, callData, jsUndefined(), arguments);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    DeferredData result;
+    result.promise = jsCast<JSPromise*>(deferred.get(exec, vm.propertyNames->builtinNames().promisePrivateName()));
+    RETURN_IF_EXCEPTION(scope, { });
+    result.resolve = jsCast<JSFunction*>(deferred.get(exec, vm.propertyNames->builtinNames().resolvePrivateName()));
+    RETURN_IF_EXCEPTION(scope, { });
+    result.reject = jsCast<JSFunction*>(deferred.get(exec, vm.propertyNames->builtinNames().rejectPrivateName()));
+    RETURN_IF_EXCEPTION(scope, { });
+
+    return result;
 }
 
-
 JSPromiseDeferred* JSPromiseDeferred::create(ExecState* exec, JSGlobalObject* globalObject)
 {
     VM& vm = exec->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
 
-    JSValue deferred = newPromiseCapability(exec, globalObject, globalObject->promiseConstructor());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-
-    JSValue promise = deferred.get(exec, vm.propertyNames->builtinNames().promisePrivateName());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-    ASSERT(promise.inherits<JSPromise>(vm));
-    JSValue resolve = deferred.get(exec, vm.propertyNames->builtinNames().resolvePrivateName());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-    JSValue reject = deferred.get(exec, vm.propertyNames->builtinNames().rejectPrivateName());
-    RETURN_IF_EXCEPTION(scope, nullptr);
-
-    return JSPromiseDeferred::create(vm, jsCast<JSPromise*>(promise), resolve, reject);
+    DeferredData data = createDeferredData(exec, globalObject, globalObject->promiseConstructor());
+    RETURN_IF_EXCEPTION(scope, { });
+    return JSPromiseDeferred::create(vm, data.promise, data.resolve, data.reject);
 }
 
-JSPromiseDeferred* JSPromiseDeferred::create(VM& vm, JSObject* promise, JSValue resolve, JSValue reject)
+JSPromiseDeferred* JSPromiseDeferred::create(VM& vm, JSPromise* promise, JSFunction* resolve, JSFunction* reject)
 {
     JSPromiseDeferred* deferred = new (NotNull, allocateCell<JSPromiseDeferred>(vm.heap)) JSPromiseDeferred(vm);
     deferred->finishCreation(vm, promise, resolve, reject);
@@ -121,7 +125,7 @@ void JSPromiseDeferred::reject(ExecState* exec, Exception* reason)
     reject(exec, reason->value());
 }
 
-void JSPromiseDeferred::finishCreation(VM& vm, JSObject* promise, JSValue resolve, JSValue reject)
+void JSPromiseDeferred::finishCreation(VM& vm, JSPromise* promise, JSFunction* resolve, JSFunction* reject)
 {
     Base::finishCreation(vm);
     m_promise.set(vm, this, promise);
index 4187a7c..49234ac 100644 (file)
 #pragma once
 
 #include "JSCast.h"
+#include "JSPromise.h"
 #include "Structure.h"
 
 namespace JSC {
 
 class Exception;
 class JSPromiseConstructor;
+class JSFunction;
 
 class JSPromiseDeferred : public JSCell {
 public:
     typedef JSCell Base;
     static const unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
 
+    struct DeferredData {
+        WTF_FORBID_HEAP_ALLOCATION;
+    public:
+        JSPromise* promise { nullptr };
+        JSFunction* resolve { nullptr };
+        JSFunction* reject { nullptr };
+    };
+    static DeferredData createDeferredData(ExecState*, JSGlobalObject*, JSPromiseConstructor*);
+
     JS_EXPORT_PRIVATE static JSPromiseDeferred* create(ExecState*, JSGlobalObject*);
-    JS_EXPORT_PRIVATE static JSPromiseDeferred* create(VM&, JSObject* promise, JSValue resolve, JSValue reject);
+    JS_EXPORT_PRIVATE static JSPromiseDeferred* create(VM&, JSPromise*, JSFunction* resolve, JSFunction* reject);
 
     static Structure* createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype)
     {
@@ -48,9 +59,9 @@ public:
 
     DECLARE_EXPORT_INFO;
 
-    JSObject* promise() const { return m_promise.get(); }
-    JSValue resolve() const { return m_resolve.get(); }
-    JSValue reject() const { return m_reject.get(); }
+    JSPromise* promise() const { return m_promise.get(); }
+    JSFunction* resolve() const { return m_resolve.get(); }
+    JSFunction* reject() const { return m_reject.get(); }
 
     JS_EXPORT_PRIVATE void resolve(ExecState*, JSValue);
     JS_EXPORT_PRIVATE void reject(ExecState*, JSValue);
@@ -62,7 +73,7 @@ public:
 
 protected:
     JSPromiseDeferred(VM&, Structure*);
-    void finishCreation(VM&, JSObject*, JSValue, JSValue);
+    void finishCreation(VM&, JSPromise*, JSFunction* resolve, JSFunction* reject);
     static void visitChildren(JSCell*, SlotVisitor&);
 
 private:
@@ -72,11 +83,9 @@ private:
     bool m_promiseIsAsyncPending { false };
 #endif
 
-    WriteBarrier<JSObject> m_promise;
-    WriteBarrier<Unknown> m_resolve;
-    WriteBarrier<Unknown> m_reject;
+    WriteBarrier<JSPromise> m_promise;
+    WriteBarrier<JSFunction> m_resolve;
+    WriteBarrier<JSFunction> m_reject;
 };
 
-JSValue newPromiseCapability(ExecState*, JSGlobalObject*, JSPromiseConstructor*);
-
 } // namespace JSC
index 895bf99..bac3273 100644 (file)
@@ -1,3 +1,22 @@
+2018-09-21  Keith Miller  <keith_miller@apple.com>
+
+        Add Promise SPI
+        https://bugs.webkit.org/show_bug.cgi?id=189809
+
+        Reviewed by Saam Barati.
+
+        Fix issue where creating a JSContextRef off the main thread before
+        creating initializing the main thread would cause an assertion
+        failure.
+
+        * wtf/MainThread.cpp:
+        (WTF::isMainThreadIfInitialized):
+        * wtf/MainThread.h:
+        * wtf/mac/MainThreadMac.mm:
+        (WTF::isMainThreadIfInitialized):
+        * wtf/text/cf/StringImplCF.cpp:
+        (WTF::StringImpl::createCFString):
+
 2018-09-20  Fujii Hironori  <Hironori.Fujii@sony.com>
 
         [Win][Clang] UNUSED_PARAM(this) causes compilation error of "cannot take the address of an rvalue of type"
index df2244d..5b40cb8 100644 (file)
@@ -73,6 +73,11 @@ bool isMainThread()
 {
     return mainThread == &Thread::current();
 }
+
+bool isMainThreadIfInitialized()
+{
+    return isMainThread();
+}
 #endif
 
 #if PLATFORM(COCOA)
index ed1d495..23f4c6d 100644 (file)
@@ -54,6 +54,7 @@ WTF_EXPORT_PRIVATE void callOnWebThreadOrDispatchAsyncOnMainThread(void (^block)
 WTF_EXPORT_PRIVATE void setMainThreadCallbacksPaused(bool paused);
 
 WTF_EXPORT_PRIVATE bool isMainThread();
+WTF_EXPORT_PRIVATE bool isMainThreadIfInitialized();
 
 WTF_EXPORT_PRIVATE bool canAccessThreadLocalDataForThread(Thread&);
 
index 270be79..cdb42af 100644 (file)
@@ -60,9 +60,9 @@ namespace WTF {
 
 static JSWTFMainThreadCaller* staticMainThreadCaller;
 static bool isTimerPosted; // This is only accessed on the main thread.
-static bool mainThreadEstablishedAsPthreadMain;
-static pthread_t mainThreadPthread;
-static NSThread* mainThreadNSThread;
+static bool mainThreadEstablishedAsPthreadMain { false };
+static pthread_t mainThreadPthread { nullptr };
+static NSThread* mainThreadNSThread { nullptr };
 
 #if USE(WEB_THREAD)
 static Thread* sApplicationUIThread;
@@ -162,6 +162,11 @@ bool isMainThread()
     return (isWebThread() || pthread_main_np()) && webThreadIsUninitializedOrLockedOrDisabled();
 }
 
+bool isMainThreadIfInitialized()
+{
+    return isMainThread();
+}
+
 bool isUIThread()
 {
     return pthread_main_np();
@@ -212,6 +217,14 @@ bool isMainThread()
     ASSERT(mainThreadPthread);
     return pthread_equal(pthread_self(), mainThreadPthread);
 }
+
+bool isMainThreadIfInitialized()
+{
+    if (mainThreadEstablishedAsPthreadMain)
+        return pthread_main_np();
+    return pthread_equal(pthread_self(), mainThreadPthread);
+}
+
 #endif // USE(WEB_THREAD)
 
 } // namespace WTF
index 53f4983..c3003ab 100644 (file)
@@ -122,7 +122,7 @@ namespace StringWrapperCFAllocator {
 
 RetainPtr<CFStringRef> StringImpl::createCFString()
 {
-    if (!m_length || !isMainThread()) {
+    if (!m_length || !isMainThreadIfInitialized()) {
         if (is8Bit())
             return adoptCF(CFStringCreateWithBytes(0, reinterpret_cast<const UInt8*>(characters8()), m_length, kCFStringEncodingISOLatin1, false));
         return adoptCF(CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar*>(characters16()), m_length));