Preconcurrency Notification names in submodules

Under concurrency=complete, the following generates a warning, and I haven't been able to figure out how to address it:

@MainActor func f() {
     _ = UIAccessibility.Notification.screenChanged // Reference to static property 'screenChanged' is not concurrency-safe because it involves shared mutable state; this is an error in Swift 6
}

It doesn't seem to help to @preconcurrency import UIKIt or UIKit.UIAccessibility. I've even tried:

@preconcurrency import struct UIKit.UIAccessibility.Notification

Is this a Swift 5.10 bug, or is there a way to mark this?

(FYI, UIAccessibility.Notification.screenChanged is not a "Notification." It's a globally writable uint32_t. Yes, it really is technically unsafe. You can assign a different value to it at runtime and really break your program. But, um, I hope no one ever does that.)

7 Likes

This looks like a compiler bug to me - I don't see any reason why a @preconcurrency import shouldn't suppress the warning. Thank you for surfacing the problem, I've filed `@preconcurrency import` doesn't suppress warning on use of imported global variable · Issue #72187 · apple/swift · GitHub to track the compiler fix.

1 Like

Thanks! Beyond suppressing the warning, is there a way for C to expose global mutable state in a way that Swift can consume it? Sometimes C libraries have global variables that are promised to be initialized just once, very early in program execution (FFT setup comes to mind). And sometimes there are global variables that you require holding a lock to access. Is there any way to annotate that on the C side or consume it on the Swift side without warnings, but without suppressing the whole file?

Yep! Clang has an attribute that can be used to tell Swift's Clang importer to apply Swift attributes or modifiers to declarations when they're imported. To apply nonisolated(unsafe) to a C global variable, you'd write __attribute__((__swift_attr__("nonisolated(unsafe)"))) (either directly or via macro) in the C header on the global. Alternatively if you wanted to formally isolate the C global to, say, the main actor, you'd use __attribute__((__swift_attr__("@MainActor"))).

4 Likes

Oh, that's perfect. I expected to have to look up some mapping of "here's the magic clang attribute for this Swift attribute." I didn't expect to just be able to put the Swift annotation inline. Very dev-friendly (once you're in a world where __attribute__ is a normal thing to write... :D)

Update: This may not be a bug. While I said I was using @preconcurrency import ..., I failed to mention that there was also a previous import SwiftUI which had already imported UIKit. Rearranging to put @preconcurrency import UIKit fixes it. This is possibly an easy mistake for devs to make, so it would be really nice if "re-import, but with @preconcurrency" worked, but I don't think it's a bug. More of a "back to C include order" situation.

2 Likes

Oh, great catch! I'll still investigate whether the compiler can detect that you wrote @preconcurrency on the import intended to bring in UIAccessibility.Notification, because I agree that's very subtle and easy to accidentally do.

1 Like

Very interesting observation.

The first example shows changing the thing that wasn't marked const in Obj-C:

// typedef uint32_t UIAccessibilityNotifications NS_TYPED_ENUM;
// UIKIT_EXTERN UIAccessibilityNotifications UIAccessibilityScreenChangedNotification;

 print(UIAccessibility.Notification.screenChanged)
// prints: UIAccessibilityNotifications(rawValue: 1000)
UIAccessibility.Notification.screenChanged = .init(rawValue: 1)
print(UIAccessibility.Notification.screenChanged)
// prints: UIAccessibilityNotifications(rawValue: 1)

The second example show that even those things marked const in Obj-C could be hacked (though probably we can't do much about it):

// typedef NSString * UIAccessibilityAssistiveTechnologyIdentifier NS_TYPED_ENUM;
// UIKIT_EXTERN UIAccessibilityAssistiveTechnologyIdentifier const UIAccessibilityNotificationSwitchControlIdentifier API_AVAILABLE(ios(8.0));

print(UIAccessibility.AssistiveTechnologyIdentifier.notificationSwitchControl)
// prints: UIAccessibilityAssistiveTechnologyIdentifier(_rawValue: UIAccessibilityNotificationSwitchControlIdentifier)
// UIAccessibility.AssistiveTechnologyIdentifier.notificationSwitchControl = ... //🛑
hack()
print(UIAccessibility.AssistiveTechnologyIdentifier.notificationSwitchControl)
// prints: UIAccessibilityAssistiveTechnologyIdentifier(_rawValue: Hello, World)
In the second case I had to hack it a bit.

option 1

void hack(void) {
    *(UIAccessibilityAssistiveTechnologyIdentifier**)&UIAccessibilityNotificationSwitchControlIdentifier = @"Hello, World";
}

option 2

let RTLD_NEXT = UnsafeMutableRawPointer(bitPattern: -1)!

func hack2() {
    let p = dlsym(RTLD_NEXT, "UIAccessibilityNotificationSwitchControlIdentifier")!
    let r = p.assumingMemoryBound(to: UIAccessibility.AssistiveTechnologyIdentifier.self)
    r.pointee = .init(rawValue: "Hello, World")
}

Thanks for pointing this out; I’ve notified the Accessibility team.

1 Like

Interestingly, Xcode 16 beta complains

'@preconcurrency' attribute on module 'UIKit' has no effect

on

@preconcurrency import struct UIKit.UIAccessibility

However if you remove @preconcurrency it says

Reference to static property 'announcement' is not concurrency-safe because it involves shared mutable state; this is an error in the Swift 6 language mode

I didn't found a way to suppress this warning on Xcode 16 beta.

2 Likes

It looks like this issue has been fixed in Xcode 16.0 beta 3 as the warnings are gone!

1 Like