Crashes in JavaScriptCore

Since a while we're receiving crash reports from JavaScriptCore. This seems to be related to us implementing WKScriptMessageHandlerWithReply (we're using the async variant). Unfortunately the crash report is not helpful. Does anybody know why this is crashing? One can see our custom code being called in step 9-11. Unfortunately we cannot reproduce, seems to happen only rarely.

Crashed: com .apple.root.user-initiated-qos.cooperative
0  JavaScriptCore                 0x110a2f8 JSC::sanitizeStackForVM(JSC::VM&) + 52
1  JavaScriptCore                 0x3c2048 JSC::JSString::create(JSC::VM&, ***::Ref<***::StringImpl, ***::RawPtrTraits<***::StringImpl> >&&) + 412
2  JavaScriptCore                 0x3c2048 JSC::JSString::create(JSC::VM&, ***::Ref<***::StringImpl, ***::RawPtrTraits<***::StringImpl> >&&) + 412
3  JavaScriptCore                 0x49fcf4 JSValueMakeString + 128
4  JavaScriptCore                 0x43b65c objectToValueWithoutCopy(JSContext*, objc_object*) + 976
5  JavaScriptCore                 0x437b14 objectToValue(JSContext*, objc_object*) + 100
6  JavaScriptCore                 0x2f6c +[JSValue valueWithObject:inContext:] + 40
7  WebKit                         0x254af8 API::SerializedScriptValue::createFromNSObject(objc_object*) + 96
8  WebKit                         0x2f2844 invocation function for block in ScriptMessageHandlerDelegate::didPostMessageWithAsyncReply(WebKit::WebPageProxy&, WebKit::FrameInfoData&&, API::ContentWorld&, WebCore::SerializedScriptValue&, ***::Function<void (API::SerializedScriptValue*, ***::String const&)>&&) + 184
9  Our App                        0x666710 thunk for @escaping @callee_unowned @convention(block) (@unowned Swift.AnyObject?, @unowned NSString?) -> () 
10 Our App                         0x666544 thunk for @escaping @callee_guaranteed (@in_guaranteed Any?, @guaranteed String?) -> ()
11 Our App                         0x6661a8 @objc closure #1 in ScriptMessageHandler.userContentController(_:didReceive:)
12 libswift_Concurrency.dylib     0x3b7cc swift::runJobInEstablishedExecutorContext(swift::Job*) + 244
13 libswift_Concurrency.dylib     0x3c1e8 swift_job_runImpl(swift::Job*, swift::ExecutorRef) + 72
14 libdispatch.dylib              0x15164 _dispatch_root_queue_drain + 396
15 libdispatch.dylib              0x1596c _dispatch_worker_thread2 + 164
16 libsystem_pthread.dylib        0x1080 _pthread_wqthread + 228
17 libsystem_pthread.dylib        0xe5c start_wqthread + 8

Accepted Reply

OK. I’d label that as “async”, and hence our earlier confusion.

Anyway, what’s happening here is that WebKit calls the delegate with a completion handler (like MyReplyHandler1) and Swift concurrency wraps your async function (MyReplyHandler2) in some glue that calls the completion handler when the async function returns. That ends up calling the completion handler from one of Swift concurrency’s worker threads. I suspect that WebKit is expecting the completion handler to be called on the main thread, and hence the crash.

Now, there’s a plethora of potential bugs to file here but before I send you off to Feedback Assistant I want to see if my theory is correct. I recommend that you switch from your current approach to the MyReplyHandler1 approach. That puts your in charge of calling the completion handler, allowing you to ensure that it’s called on the main thread.

So, something like this:

class MyReplyHandler1: NSObject, WKScriptMessageHandlerWithReply {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
        Task {
            let reply: Any? = … your code here …
            let errorMessage: String? = … your code here …
            DispatchQueue.main.async {
                replyHandler(reply, errorMessage)
            }
        }
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Replies

Does anybody know why this is crashing?

Please post a full crash report, using the instructions in Posting a Crash Report.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I wanted to share a crash report but before I did I inspected it once more in detail (with 3rd party editor rather than Xcode) and could see some information that are not included when inspecting from Xcode Crashes view:

[...]
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: SIGNAL 11 Segmentation fault: 11
Terminating Process: exc handler [43798]
Triggered by Thread:  10
[...]

So what's interesting here is that this is actually not crashing in JavaScripCore but it's a segmentation fault (The way the crash report is shown in Xcode is misleading). We used to get a "segmentation fault: 11" every time when implementing the async/ await API of WKScriptMessageHandlerWithReply with Xcode 13 as I stated here.

It seemed to be resolved as of Xcode 14, but now it simply happens only occasionally (We're unable to reproduce but our users are). So my best guess for now is that one shouldn't use the async variant of the WKScriptMessageHandlerWithReply API but wait for  to properly fix it.

Update: By now we rolled out an update of our app including the change to use the non async/await API of WKScriptMessageHandlerWithReply. Unfortunately it doesn't make a difference. Any other ideas how to solve this Segmentation Fault 11 crash are most welcome!

Update @eskimo: I shared the crash logs with you as discussed.

I shared the crash logs with you as discussed.

Thanks.

By now we rolled out an update of our app including the change to use the non async/await API of WKScriptMessageHandlerWithReply. Unfortunately it doesn't make a difference.

Do any of the crash reports you sent me reflect that change? ’cause as far as I can tell they’re all doing async replies.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Do any of the crash reports you sent me reflect that change? ’cause as far as I can tell they’re all doing async replies.

They should. But to make sure I verified and sent some that definitely do!

Hmmm, I need more guidance here. Every crash report you sent me seems to have a backtrace like this:

Thread 7 name:
Thread 7 Crashed:
…
2  WebKit                     … ___ZN28ScriptMessageHandlerDelegate28didPostMessageWithAsyncReplyER…
3  tttt                       …
4  tttt                       …
5  tttt                       …
6  tttt                       …
7  tttt                       …
8  tttt                       …
9  tttt                       …
10 libswift_Concurrency.dylib … completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) + 1 …

You’re crashing in WebKit (frame 2) which was called by your code (frames 3 through 9) which was called by the Swift concurrency runtime. How can that be if you’re using the “non async/await API”?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Hmm that doesn't make any sense to me. If you check the same crash report you can see in frame 6 that this was called from the non async handler, i.e. replyHandler:

6   tttt           ... closure #1 in ScriptMessageHandler.userContentController(_:didReceive:replyHandler:) + 64 (ScriptMessageHandler.swift:57)

-> In other words it is stating that the non async API userContentController(_:didReceive:replyHandler:) is invoked. Why should it report later (in frame 2) that we're using the async handler.

Maybe the "AsyncReply" part of the message ___ZN28ScriptMessageHandlerDelegate28didPostMessageWithAsyncReplyER is referring to the fact that this is an asynchronous API (using a closure) and not that it is using the async/await concurrency language feature?

I suspect we’re talking at cross purposes here, so I’d like to nail down some terminology. There are three ways you can write a script message handler:

class MyNoReplyHandler: NSObject, WKScriptMessageHandler {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        fatalError()
    }
}

class MyReplyHandler1: NSObject, WKScriptMessageHandlerWithReply {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
        fatalError()
    }
}

class MyReplyHandler2: NSObject, WKScriptMessageHandlerWithReply {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) async -> (Any?, String?) {
        fatalError()
    }
}

Which are you using?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

We're using the third one: MyReplyHandler2

OK. I’d label that as “async”, and hence our earlier confusion.

Anyway, what’s happening here is that WebKit calls the delegate with a completion handler (like MyReplyHandler1) and Swift concurrency wraps your async function (MyReplyHandler2) in some glue that calls the completion handler when the async function returns. That ends up calling the completion handler from one of Swift concurrency’s worker threads. I suspect that WebKit is expecting the completion handler to be called on the main thread, and hence the crash.

Now, there’s a plethora of potential bugs to file here but before I send you off to Feedback Assistant I want to see if my theory is correct. I recommend that you switch from your current approach to the MyReplyHandler1 approach. That puts your in charge of calling the completion handler, allowing you to ensure that it’s called on the main thread.

So, something like this:

class MyReplyHandler1: NSObject, WKScriptMessageHandlerWithReply {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) {
        Task {
            let reply: Any? = … your code here …
            let errorMessage: String? = … your code here …
            DispatchQueue.main.async {
                replyHandler(reply, errorMessage)
            }
        }
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for your update. Sorry I confused something, we were using MyReplyHandler2 before, but changed to MyReplyHandler1 solution. I'll try your recommendation to dispatch the reply handler to main thread as you recommended and see if this helps.

By now we've used this change (to reply on main thread) for a few days and no longer see these crashes. Thank you!

I guess it would make sense to fix this for WKScriptMessageHandlerWithReply or at least add a warning to the documentation (also for older versions).

no longer see these crashes.

Yay!

I guess it would make sense to fix this for WKScriptMessageHandlerWithReply or at least add a warning to the documentation (also for older versions).

Right. I see two potential bugs here:

  • The docs should make it clear that the replyHandler parameter to -userContentController:didReceiveScriptMessage:replyHandler: is intended to be called on the main thread.

  • That parameter should have a ‘main actor’ annotation so that Swift concurrency does the right thing automatically.

I encourage you to file bugs for both of these.

Please post your bug numbers, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Add a Comment