1- use alloc:: ffi:: CString ;
21#[ cfg( debug_assertions) ]
32use alloc:: vec:: Vec ;
3+ use core:: ffi:: CStr ;
44use core:: marker:: PhantomData ;
55use core:: panic:: { RefUnwindSafe , UnwindSafe } ;
66#[ cfg( debug_assertions) ]
@@ -15,7 +15,10 @@ use crate::runtime::{
1515use crate :: runtime:: { AnyProtocol , MethodDescription } ;
1616use crate :: { AnyThread , ClassType , DefinedClass , Message , ProtocolType } ;
1717
18- use super :: defined_ivars:: { register_with_ivars, setup_dealloc} ;
18+ use super :: defined_ivars:: {
19+ drop_flag_offset, ivar_drop_flag_names, ivars_offset, register_drop_flag, register_ivars,
20+ setup_dealloc,
21+ } ;
1922use super :: { CopyFamily , InitFamily , MutableCopyFamily , NewFamily , NoneFamily } ;
2023
2124/// Helper for determining auto traits of defined classes.
@@ -184,42 +187,132 @@ impl<T: Message> MaybeOptionRetained for Option<Retained<T>> {
184187 }
185188}
186189
187- #[ derive( Debug ) ]
188- pub struct ClassBuilderHelper < T : ?Sized > {
189- builder : ClassBuilder ,
190- p : PhantomData < T > ,
190+ /// Convert a class name with a trailing NUL byte to a `CStr`, at `const`.
191+ #[ track_caller]
192+ pub const fn class_c_name ( name : & str ) -> & CStr {
193+ let bytes = name. as_bytes ( ) ;
194+ // Workaround for `from_bytes_with_nul` not being `const` in MSRV.
195+ let mut i = 0 ;
196+ while i < bytes. len ( ) - 1 {
197+ if bytes[ i] == 0 {
198+ panic ! ( "class name must not contain interior NUL bytes" ) ;
199+ }
200+ i += 1 ;
201+ }
202+ if let Ok ( c_name) = CStr :: from_bytes_until_nul ( bytes) {
203+ c_name
204+ } else {
205+ unreachable ! ( )
206+ }
191207}
192208
193- // Outlined for code size
209+ // Kept separate for code size.
194210#[ track_caller]
195- fn create_builder ( name : & str , superclass : & AnyClass ) -> ClassBuilder {
196- let c_name = CString :: new ( name) . expect ( "class name must be UTF-8" ) ;
197- match ClassBuilder :: new ( & c_name, superclass) {
198- Some ( builder) => builder,
199- None => panic ! (
200- "could not create new class {name}. Perhaps a class with that name already exists?"
201- ) ,
202- }
211+ fn class_not_present ( c_name : & CStr ) -> ! {
212+ panic ! ( "could not create new class {c_name:?}, though there was no other class with that name" )
203213}
204214
205- impl < T : DefinedClass > ClassBuilderHelper < T > {
206- #[ inline]
207- #[ track_caller]
208- #[ allow( clippy:: new_without_default) ]
209- pub fn new ( ) -> Self
210- where
211- T :: Super : ClassType ,
212- {
213- let mut builder = create_builder ( T :: NAME , <T :: Super as ClassType >:: class ( ) ) ;
215+ #[ track_caller]
216+ fn class_not_unique ( c_name : & CStr ) -> ! {
217+ panic ! ( "could not create new class {c_name:?}, perhaps a class with that name already exists?" )
218+ }
214219
215- setup_dealloc :: < T > ( & mut builder) ;
220+ #[ inline]
221+ #[ track_caller]
222+ #[ allow( clippy:: new_without_default) ]
223+ pub fn define_class < T : DefinedClass > (
224+ c_name : & CStr ,
225+ name_is_auto_generated : bool ,
226+ register_impls : impl FnOnce ( & mut ClassBuilderHelper < T > ) ,
227+ ) -> ( & ' static AnyClass , isize , isize )
228+ where
229+ T :: Super : ClassType ,
230+ {
231+ let ( ivar_name, drop_flag_name) = ivar_drop_flag_names :: < T > ( ) ;
216232
217- Self {
233+ let superclass = <T :: Super as ClassType >:: class ( ) ;
234+ let cls = if let Some ( builder) = ClassBuilder :: new ( c_name, superclass) {
235+ let mut this = ClassBuilderHelper {
218236 builder,
219237 p : PhantomData ,
238+ } ;
239+
240+ setup_dealloc :: < T > ( & mut this. builder ) ;
241+
242+ register_impls ( & mut this) ;
243+
244+ register_ivars :: < T > ( & mut this. builder , & ivar_name) ;
245+ register_drop_flag :: < T > ( & mut this. builder , & drop_flag_name) ;
246+
247+ this. builder . register ( )
248+ } else {
249+ // When loading two dynamic libraries that both use a class from some
250+ // shared (static) Rust library, the dynamic linker will duplicate the
251+ // statics that `define_class!` defines.
252+ //
253+ // For most statics that people create, this is the desired behaviour.
254+ //
255+ // In our case though, there is only a single Objective-C runtime with
256+ // a single list of classes, and thus those two dynamic libraries end
257+ // up trying to register the same class multiple times.
258+ //
259+ // To support such use-cases, we assume that the existing class is the
260+ // one that we want, and don't try to declare it ourselves.
261+ //
262+ // This is **sound** within the context of a single linker invocation,
263+ // since we ensure in `define_class!` with `#[extern_name = ...]` that
264+ // the class name is unique.
265+ //
266+ // It is **unsound** in the case above of multiple dynamic libraries,
267+ // since we cannot guarantee that the name actually comes from the
268+ // same piece of code. The dynamic linker already does this same merge
269+ // operation though, so we will consider this a non-issue (i.e. the
270+ // same problem already exists in Objective-C w. dynamic libraries).
271+ //
272+ // See <https://github.com/rust-windowing/raw-window-metal/issues/29>
273+ // for more details on the use-case.
274+ //
275+ // NOTE: We _could_ also solve this by autogenerating a class name
276+ // based on the address of a static - but in the future, we would like
277+ // to generate fully static classes, and then such a solution wouldn't
278+ // be possible.
279+ //
280+ // ---
281+ //
282+ // We only do this by default when we've auto-generated the name,
283+ // since here we'll be reasonably sure that it's unique to that
284+ // specific piece of code.
285+ //
286+ // In the future, we might be able to relax this to also work with
287+ // user-specified names, though we'd have to somehow ensure that the
288+ // name isn't something crazy like "NSObject".
289+ //
290+ // We also have an (intentionally undocumented) workaround env var in
291+ // case this becomes a problem for users in the future.
292+ let overridden = option_env ! ( "UNSAFE_OBJC2_ALLOW_CLASS_OVERRIDE" ) == Some ( "1" ) ;
293+ if name_is_auto_generated || overridden {
294+ AnyClass :: get ( c_name) . unwrap_or_else ( || class_not_present ( c_name) )
295+ } else {
296+ class_not_unique ( c_name)
220297 }
221- }
298+ } ;
299+
300+ // Pass class and offsets back to allow storing them in statics for faster
301+ // subsequent access.
302+ (
303+ cls,
304+ ivars_offset :: < T > ( cls, & ivar_name) ,
305+ drop_flag_offset :: < T > ( cls, & drop_flag_name) ,
306+ )
307+ }
222308
309+ #[ derive( Debug ) ]
310+ pub struct ClassBuilderHelper < T : ?Sized > {
311+ builder : ClassBuilder ,
312+ p : PhantomData < T > ,
313+ }
314+
315+ impl < T : DefinedClass > ClassBuilderHelper < T > {
223316 #[ inline]
224317 pub fn add_protocol_methods < P > ( & mut self ) -> ClassProtocolMethodsBuilder < ' _ , T >
225318 where
@@ -279,11 +372,6 @@ impl<T: DefinedClass> ClassBuilderHelper<T> {
279372 // SAFETY: Checked by caller
280373 unsafe { self . builder . add_class_method ( sel, func) }
281374 }
282-
283- #[ inline]
284- pub fn register ( self ) -> ( & ' static AnyClass , isize , isize ) {
285- register_with_ivars :: < T > ( self . builder )
286- }
287375}
288376
289377/// Helper for ensuring that:
0 commit comments