RESOLVED FIXED 205654
Make _callAsyncFunction:withArguments: work with promises
https://bugs.webkit.org/show_bug.cgi?id=205654
Summary Make _callAsyncFunction:withArguments: work with promises
Brady Eidson
Reported 2019-12-30 21:56:54 PST
Make _callAsyncFunction:withArguments: work with promises
Attachments
Patch (13.35 KB, patch)
2020-01-06 14:30 PST, Brady Eidson
no flags
Patch (9.30 KB, patch)
2020-01-06 16:03 PST, Brady Eidson
no flags
Patch (9.35 KB, patch)
2020-01-07 16:07 PST, Brady Eidson
no flags
Patch (9.31 KB, patch)
2020-01-07 22:38 PST, Brady Eidson
no flags
Patch (9.39 KB, patch)
2020-01-08 11:21 PST, Brady Eidson
no flags
Brady Eidson
Comment 1 2020-01-06 14:30:25 PST
Saam Barati
Comment 2 2020-01-06 14:57:20 PST
Comment on attachment 386888 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=386888&action=review r=me > Source/WTF/wtf/SharedFunction.h:36 > +class SharedFunction<Out(In...)> : public RefCounted<SharedFunction<Out(In...)>> { I think instead of this we can just use SharedTask > Source/WebCore/bindings/js/ScriptController.cpp:715 > + auto thenIdentifier = world.vm().propertyNames->builtinNames().thenPrivateName(); this should be: "world.vm().propertyNames->then". A test that would catch this is using your own "thenable" > Source/WebCore/bindings/js/ScriptController.cpp:719 > + if (!thenFunction.isFunction(world.vm())) { The right check here is: CallData ignoredCallData; if (thenFunction.isObject && asObject(thenFunction)->methodTable(vm)->getCallData(asObject(thenFunction), ignoredCallData) != CallType::None) I think a test for a callable which does not pass the isFunction() query would be something like this with a Proxy as the "thenFunction": let p = new Proxy(function() { print("this should be called"); }, { }); let thenable = {then: p}; > Source/WebCore/bindings/js/ScriptController.cpp:740 > + auto finalizeCount = makeUniqueWithoutFastMallocCheck<unsigned>(0); nit: can you change makeUnique to just check if T is a primitive? > Source/WebCore/bindings/js/ScriptController.cpp:747 > + if (heap) { these branches are not needed. > Source/WebCore/bindings/js/ScriptController.cpp:754 > + if (heap) { ditto. > Source/WebCore/bindings/js/ScriptController.cpp:756 > + finalizeGuard.get()(); instead of getting the heap pointer here, you can just do: world.vm().heap
Brady Eidson
Comment 3 2020-01-06 16:03:03 PST
Brady Eidson
Comment 4 2020-01-07 08:51:03 PST
The problem with these test runs is spamming to the console from the AsyncFunction.Promise test affecting later runs. Damn. Fixing.
Brady Eidson
Comment 5 2020-01-07 16:07:05 PST
Brady Eidson
Comment 6 2020-01-07 16:07:36 PST
(In reply to Brady Eidson from comment #4) > The problem with these test runs is spamming to the console from the > AsyncFunction.Promise test affecting later runs. > > Damn. > > Fixing. Nope that wasn't it. Accidentally applying the new async behavior to everybody. Fixed in most recent upload.
Brady Eidson
Comment 7 2020-01-07 21:58:39 PST
That fixed the collateral damage to other API tests but revealed that the original AsyncFunction tests are now busted. I can't quite figure out why, other than verify locally before this patch they work, and with it applied they don't. It's a problem with serialization. Before my patch, a JSC::JSValue representing "undefined" successfully serializes. Same for "null" With my patch, those exact same JSValues do not serialize.
Brady Eidson
Comment 8 2020-01-07 22:04:08 PST
For the "null" and "undefined" jsvalues, SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception) succeeds before my patch It fails after my patch because it has an exception.
Brady Eidson
Comment 9 2020-01-07 22:12:09 PST
Here's the body of SerializedScriptValue::create. With my patch, an exception occurs in here. Without my patch, it doesn't. RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception) { JSGlobalObject* lexicalGlobalObject = toJS(originContext); VM& vm = lexicalGlobalObject->vm(); JSLockHolder locker(vm); auto scope = DECLARE_CATCH_SCOPE(vm); JSValue value = toJS(lexicalGlobalObject, apiValue); ------> Exception happens in the above line auto serializedValue = SerializedScriptValue::create(*lexicalGlobalObject, value); if (UNLIKELY(scope.exception())) { if (exception) *exception = toRef(lexicalGlobalObject, scope.exception()->value()); scope.clearException(); return nullptr; } ASSERT(serializedValue); return serializedValue; }
Brady Eidson
Comment 10 2020-01-07 22:18:39 PST
(In reply to Brady Eidson from comment #9) > Here's the body of SerializedScriptValue::create. > > With my patch, an exception occurs in here. > Without my patch, it doesn't. > > RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef > originContext, JSValueRef apiValue, JSValueRef* exception) > { > JSGlobalObject* lexicalGlobalObject = toJS(originContext); > VM& vm = lexicalGlobalObject->vm(); > JSLockHolder locker(vm); > auto scope = DECLARE_CATCH_SCOPE(vm); > > JSValue value = toJS(lexicalGlobalObject, apiValue); > > ------> Exception happens in the above line > > auto serializedValue = > SerializedScriptValue::create(*lexicalGlobalObject, value); > if (UNLIKELY(scope.exception())) { > if (exception) > *exception = toRef(lexicalGlobalObject, > scope.exception()->value()); > scope.clearException(); > return nullptr; > } > ASSERT(serializedValue); > return serializedValue; > } Nope, I was wrong - the exception has already occurred the moment you make the DECLARE_CATCH_SCOPE. This must be because of my patch's attempt to pull the "then" property from the result.
Brady Eidson
Comment 11 2020-01-07 22:24:56 PST
(In reply to Brady Eidson from comment #10) > (In reply to Brady Eidson from comment #9 > > Nope, I was wrong - the exception has already occurred the moment you make > the DECLARE_CATCH_SCOPE. > > This must be because of my patch's attempt to pull the "then" property from > the result. Confirmed. By smattering exception scope checks throughout ScriptController::executeAsynchronousUserAgentScriptInWorld I found it occurs on this line: auto thenFunction = result.value().get(&globalObject, thenIdentifier);
Brady Eidson
Comment 12 2020-01-07 22:26:04 PST
(In reply to Brady Eidson from comment #11) > (In reply to Brady Eidson from comment #10) > > (In reply to Brady Eidson from comment #9 > > > > Nope, I was wrong - the exception has already occurred the moment you make > > the DECLARE_CATCH_SCOPE. > > > > This must be because of my patch's attempt to pull the "then" property from > > the result. > > Confirmed. By smattering exception scope checks throughout > ScriptController::executeAsynchronousUserAgentScriptInWorld I found it > occurs on this line: > > auto thenFunction = result.value().get(&globalObject, thenIdentifier); It appears doing that on a JSNull or JSUndefined throws an exception. Will try my own fix and make sure Saam takes another look.
Brady Eidson
Comment 13 2020-01-07 22:38:09 PST
Brady Eidson
Comment 14 2020-01-07 22:39:12 PST
(In reply to Brady Eidson from comment #13) > Created attachment 387079 [details] > Patch Basically added an "isNullOrUndefined()" check on the value before we try to extract "then" from it.
Saam Barati
Comment 15 2020-01-07 22:53:54 PST
Comment on attachment 387079 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=387079&action=review > Source/WebCore/bindings/js/ScriptController.cpp:718 > + auto thenFunction = result.value().get(&globalObject, thenIdentifier); I think the appropriate check is just to check for exceptions after this (and maybe omit the isUndefinedOrNull, since that'll just fall out from this). (Even if result.value() isn't null or undefined, this "get" may run arbitrary JS code, and can throw. For. example, when it's a getter). I think you want to declare a catch scope, and return early (and maybe reject?) if an exception is thrown. The only other place where an exception might get thrown is your JS function call at the bottom. However, since that's the final thing in this function, you can call "release" on your exception scope before doing the JS call.
Brady Eidson
Comment 16 2020-01-07 23:12:15 PST
(In reply to Saam Barati from comment #15) > Comment on attachment 387079 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=387079&action=review > > > Source/WebCore/bindings/js/ScriptController.cpp:718 > > + auto thenFunction = result.value().get(&globalObject, thenIdentifier); > > I think the appropriate check is just to check for exceptions after this > (and maybe omit the isUndefinedOrNull, since that'll just fall out from > this). > (Even if result.value() isn't null or undefined, this "get" may run > arbitrary JS code, and can throw. For. example, when it's a getter). Okay, but... > I think you want to declare a catch scope, and return early (and maybe > reject?) if an exception is thrown. No, because it's 100% valid to return null or undefined and pass them back to the UI process. So if looking up "then" on a real object can run arbitrary JS code and throw an exception, we definitely should pass that back to the UI process as an error. But looking up "then" on null or undefined should silently fail, and we need to pass back null or undefined to the UI process. I'm guessing that it's impossible to detect *why* an exception was thrown in the get() call, and therefore I need to prequalify the value beforehand, like in this patch? Or is there something else I'm missing?
Brady Eidson
Comment 17 2020-01-08 07:29:40 PST
Note: The api-ios failures here are not due to this patch.
Brady Eidson
Comment 18 2020-01-08 11:12:25 PST
Pow-wow'ed with Keith - Should do an isObject check before doing the get (This is what some of the related ES spec algorithms do)
Brady Eidson
Comment 19 2020-01-08 11:21:48 PST
Saam Barati
Comment 20 2020-01-08 11:41:02 PST
(In reply to Brady Eidson from comment #16) > (In reply to Saam Barati from comment #15) > > Comment on attachment 387079 [details] > > Patch > > > > View in context: > > https://bugs.webkit.org/attachment.cgi?id=387079&action=review > > > > > Source/WebCore/bindings/js/ScriptController.cpp:718 > > > + auto thenFunction = result.value().get(&globalObject, thenIdentifier); > > > > I think the appropriate check is just to check for exceptions after this > > (and maybe omit the isUndefinedOrNull, since that'll just fall out from > > this). > > (Even if result.value() isn't null or undefined, this "get" may run > > arbitrary JS code, and can throw. For. example, when it's a getter). > > Okay, but... > > > I think you want to declare a catch scope, and return early (and maybe > > reject?) if an exception is thrown. > > No, because it's 100% valid to return null or undefined and pass them back > to the UI process. I see. I didn't realize that. What Keith suggested to you seems reasonable to me too. > > So if looking up "then" on a real object can run arbitrary JS code and throw > an exception, we definitely should pass that back to the UI process as an > error. > > But looking up "then" on null or undefined should silently fail, and we need > to pass back null or undefined to the UI process. > > I'm guessing that it's impossible to detect *why* an exception was thrown in > the get() call, and therefore I need to prequalify the value beforehand, > like in this patch? > > Or is there something else I'm missing?
Saam Barati
Comment 21 2020-01-08 11:41:41 PST
Comment on attachment 387116 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=387116&action=review > Source/WebCore/bindings/js/ScriptController.cpp:718 > + auto thenFunction = result.value().get(&globalObject, thenIdentifier); I think you still want an exception check after this.
Brady Eidson
Comment 22 2020-01-08 12:27:19 PST
(In reply to Saam Barati from comment #21) > Comment on attachment 387116 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=387116&action=review > > > Source/WebCore/bindings/js/ScriptController.cpp:718 > > + auto thenFunction = result.value().get(&globalObject, thenIdentifier); > > I think you still want an exception check after this. As mentioned, the SerializedScriptValue creator handles this. It would be nicer to catch the native exception, and I'll do it in a followup
WebKit Commit Bot
Comment 23 2020-01-08 13:47:25 PST
Comment on attachment 387116 [details] Patch Clearing flags on attachment: 387116 Committed r254222: <https://trac.webkit.org/changeset/254222>
WebKit Commit Bot
Comment 24 2020-01-08 13:47:27 PST
All reviewed patches have been landed. Closing bug.
Radar WebKit Bug Importer
Comment 25 2020-01-08 13:48:16 PST
Truitt Savell
Comment 26 2020-01-09 09:01:02 PST
Brady Eidson
Comment 27 2020-01-13 13:26:42 PST
(In reply to Truitt Savell from comment #26) > It looks like the changes in https://trac.webkit.org/changeset/254222/webkit > > introduced a Timing out API test TestWebKitAPI.AsyncFunction.Promise > > https://results.webkit.org/?suite=api-tests&test=TestWebKitAPI.AsyncFunction. > Promise > > tracked in https://bugs.webkit.org/show_bug.cgi?id=206012 Yet Another Case™ of EWS not agreeing with the bots. Will take a look in that other bug.
Jonathan Bedard
Comment 28 2020-01-17 09:28:44 PST
(In reply to Brady Eidson from comment #27) > (In reply to Truitt Savell from comment #26) > > It looks like the changes in https://trac.webkit.org/changeset/254222/webkit > > > > introduced a Timing out API test TestWebKitAPI.AsyncFunction.Promise > > > > https://results.webkit.org/?suite=api-tests&test=TestWebKitAPI.AsyncFunction. > > Promise > > > > tracked in https://bugs.webkit.org/show_bug.cgi?id=206012 > > Yet Another Case™ of EWS not agreeing with the bots. > > Will take a look in that other bug. It looks like it's only on Debug builds, EWS tests Release.
Note You need to log in before you can comment on or make changes to this bug.